Skip to content

Implement Timeline Visualization in GUI#63

Open
nur-srijan wants to merge 7 commits intomainfrom
timeline-visualization-14868285181004966445
Open

Implement Timeline Visualization in GUI#63
nur-srijan wants to merge 7 commits intomainfrom
timeline-visualization-14868285181004966445

Conversation

@nur-srijan
Copy link
Copy Markdown
Owner

@nur-srijan nur-srijan commented Feb 9, 2026

Implemented the timeline visualization feature in the GUI application.
This involved:

  1. Modifying src/replay.rs to expose a get_events method on ReplayEngine, allowing access to the raw event data needed for visualization.
  2. Updating src/bin/gui.rs:
    • Added replay_events to the TimeLoopGui state.
    • Populated replay_events when a session is selected.
    • Replaced the placeholder "Event timeline visualization would go here" with actual rendering logic using egui.
    • The visualization draws a timeline bar where events are represented as colored lines/ticks positioned according to their timestamp relative to the session duration.
    • Different event types are color-coded (e.g., Commands are blue, FileChanges are red).
    • A playback cursor indicates the current position in the session.
    • Added a legend to explain the color coding.

Verified the changes by running cargo check --features gui and cargo clippy --features gui to ensure no compilation errors or lints were introduced in the modified files. Also ran existing tests with cargo test --lib.


PR created automatically by Jules for task 14868285181004966445 started by @nur-srijan


Important

Adds timeline visualization to GUI, rendering events from ReplayEngine as color-coded lines using egui.

  • Behavior:
    • Adds get_events() method to ReplayEngine in src/replay.rs to retrieve event data for visualization.
    • Updates TimeLoopGui in src/bin/gui.rs to include replay_events state and populate it when a session is selected.
    • Implements timeline visualization in src/bin/gui.rs using egui, rendering events as color-coded lines based on their type.
    • Adds a playback cursor and a legend for event types in the timeline.
  • Verification:
    • Verified with cargo check --features gui and cargo clippy --features gui.
    • Ran existing tests with cargo test --lib.

This description was created by Ellipsis for 64b2feb. You can customize this summary. It will automatically update as commits are pushed.

Replaced the placeholder text in `src/bin/gui.rs` with a visual representation of events.
- Added `get_events` method to `ReplayEngine` in `src/replay.rs` to expose session events.
- Updated `TimeLoopGui` to store and load `replay_events`.
- Implemented timeline drawing in `show_session_details` using `egui::Painter`.
- Visualized different event types (Command, FileChange, etc.) with distinct colors.
- Added a playback position indicator on the timeline.

Co-authored-by: nur-srijan <198181700+nur-srijan@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copy link
Copy Markdown

@ellipsis-dev ellipsis-dev Bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to 64b2feb in 8 seconds. Click for details.
  • Reviewed 146 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_Tb6Zm09gFnQqNOtS

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @nur-srijan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates a new timeline visualization feature into the GUI application. It allows users to graphically observe the sequence and timing of various events within a selected replay session, enhancing the understanding of session activity and playback progression.

Highlights

  • Exposed Event Data: A new get_events method was added to the ReplayEngine in src/replay.rs, allowing the GUI to access raw event data for visualization.
  • GUI State Update: The TimeLoopGui struct in src/bin/gui.rs now includes a replay_events field to store the events associated with a selected session.
  • Event Loading on Session Select: The replay_events are now populated when a session is selected, ensuring that the visualization has the necessary data to render.
  • Timeline Rendering Implementation: The placeholder for timeline visualization in src/bin/gui.rs has been replaced with actual rendering logic using egui.
  • Visual Event Representation: Events are displayed as colored lines or ticks on a timeline, with their positions determined by their timestamps relative to the session's duration.
  • Color-Coded Event Types: Different event types, such as Commands and FileChanges, are assigned distinct colors on the timeline for easy identification.
  • Playback Cursor: A visual cursor is now rendered on the timeline to indicate the current playback position within the session.
  • Interactive Legend: A legend has been added to the GUI to explain the color coding used for various event types on the timeline.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/bin/gui.rs
    • Added Event and EventType imports from timeloop_terminal.
    • Introduced replay_events: Vec<Event> field to the TimeLoopGui struct.
    • Initialized replay_events as an empty vector in the Default implementation for TimeLoopGui.
    • Modified the load_session_data method to fetch and store events using the new ReplayEngine::get_events() method.
    • Replaced the placeholder label "Event timeline visualization would go here" with comprehensive egui rendering logic for the timeline.
    • Implemented drawing of event markers with varying colors, heights, and stroke widths based on EventType.
    • Added a white playback cursor that moves along the timeline based on self.position_ms.
    • Included a legend to clarify the color coding for Command, File Change, and Terminal State events.
    • Added a check for zero session duration to prevent division by zero and display an appropriate message.
  • src/replay.rs
    • Added a new public method get_events(&self) -> crate::Result<Vec<Event>> to ReplayEngine, which retrieves all events for the current session from storage.
Activity
  • The pull request was automatically generated by Jules for a task initiated by @nur-srijan.
  • The author performed local verification steps, including cargo check --features gui, cargo clippy --features gui, and cargo test --lib, to ensure code quality and correctness.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully implements the timeline visualization in the GUI. The changes are well-structured, introducing a new get_events method in the ReplayEngine and using it to render a timeline with egui. My review focuses on a performance issue in data loading, improving UI state consistency, and some minor improvements for maintainability and user experience in the new visualization code. Overall, a great addition to the GUI.

Comment thread src/bin/gui.rs
Comment on lines 332 to 345
if let Ok(engine) = ReplayEngine::new(session_id) {
if let Ok(events) = engine.get_events() {
self.replay_events = events;
} else {
self.replay_events.clear();
}

if let Ok(rs) = engine.get_session_summary() {
self.replay_summary = Some(rs);
}
} else {
self.replay_events.clear();
self.replay_summary = None;
}
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.

high

This block for loading session data has a significant performance issue and can be made more robust.

Performance: engine.get_events() and engine.get_session_summary() both internally read and parse all events from storage. This means you are doing the same expensive work twice every time a session is selected. For large sessions, this will be slow. I recommend refactoring ReplayEngine to calculate the summary from the already-loaded events to avoid this.

Robustness: The error handling can lead to an inconsistent UI state and can be simplified. The suggested change improves robustness, but the performance issue will remain until ReplayEngine is refactored.

        if let Ok(engine) = ReplayEngine::new(session_id) {
            if let Ok(events) = engine.get_events() {
                self.replay_summary = engine.get_session_summary().ok();
                self.replay_events = events;
            } else {
                self.replay_events.clear();
                self.replay_summary = None;
            }
        } else {
            self.replay_events.clear();
            self.replay_summary = None;
        }

Comment thread src/bin/gui.rs
Comment on lines +514 to +520
let (color, height_fraction, y_offset) = match event.event_type {
EventType::Command { .. } => (egui::Color32::from_rgb(100, 149, 237), 0.8, 0.1), // Cornflower Blue
EventType::FileChange { .. } => (egui::Color32::from_rgb(255, 99, 71), 0.8, 0.1), // Tomato Red
EventType::TerminalState { .. } => (egui::Color32::from_gray(100), 0.4, 0.3),
EventType::KeyPress { .. } => (egui::Color32::from_gray(60), 0.2, 0.4),
EventType::SessionMetadata { .. } => (egui::Color32::WHITE, 0.5, 0.25),
};
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.

medium

The colors and layout properties for different event types are hardcoded as magic values within this match statement. To improve readability and maintainability, it's better to define these as named constants, perhaps at the top of the file or in a dedicated constants module. This makes it easier to see all visualization parameters in one place and change them consistently.

Comment thread src/bin/gui.rs
nur-srijan and others added 2 commits February 16, 2026 16:33
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@nur-srijan
Copy link
Copy Markdown
Owner Author

@jules Comment on lines 332 to 345 of src/bin/gui.rs: This block for loading session data has a significant performance issue and can be made more robust.

Performance: engine.get_events() and engine.get_session_summary() both internally read and parse all events from storage. This means you are doing the same expensive work twice every time a session is selected. For large sessions, this will be slow. I recommend refactoring ReplayEngine to calculate the summary from the already-loaded events to avoid this.

Robustness: The error handling can lead to an inconsistent UI state and can be simplified. The suggested change improves robustness, but the performance issue will remain until ReplayEngine is refactored.

        if let Ok(engine) = ReplayEngine::new(session_id) {
            if let Ok(events) = engine.get_events() {
                self.replay_summary = engine.get_session_summary().ok();
                self.replay_events = events;
            } else {
                self.replay_events.clear();
                self.replay_summary = None;
            }
        } else {
            self.replay_events.clear();
            self.replay_summary = None;
        }

Lines +514 to +520: The colors and layout properties for different event types are hardcoded as magic values within this match statement. To improve readability and maintainability, it's better to define these as named constants, perhaps at the top of the file or in a dedicated constants module. This makes it easier to see all visualization parameters in one place and change them consistently.

@google-labs-jules
Copy link
Copy Markdown
Contributor

@jules Comment on lines 332 to 345 of src/bin/gui.rs: This block for loading session data has a significant performance issue and can be made more robust.

Performance: engine.get_events() and engine.get_session_summary() both internally read and parse all events from storage. This means you are doing the same expensive work twice every time a session is selected. For large sessions, this will be slow. I recommend refactoring ReplayEngine to calculate the summary from the already-loaded events to avoid this.

Robustness: The error handling can lead to an inconsistent UI state and can be simplified. The suggested change improves robustness, but the performance issue will remain until ReplayEngine is refactored.

        if let Ok(engine) = ReplayEngine::new(session_id) {
            if let Ok(events) = engine.get_events() {
                self.replay_summary = engine.get_session_summary().ok();
                self.replay_events = events;
            } else {
                self.replay_events.clear();
                self.replay_summary = None;
            }
        } else {
            self.replay_events.clear();
            self.replay_summary = None;
        }

Lines +514 to +520: The colors and layout properties for different event types are hardcoded as magic values within this match statement. To improve readability and maintainability, it's better to define these as named constants, perhaps at the top of the file or in a dedicated constants module. This makes it easier to see all visualization parameters in one place and change them consistently.

Thank you for the detailed feedback. I have addressed both points:

  1. Performance & Robustness: I refactored ReplayEngine in src/replay.rs to expose a static calculate_summary(events: &[Event]) method. In src/bin/gui.rs, I updated the session loading logic to fetch events once using engine.get_events() and then compute the summary from those events in-memory, avoiding a redundant disk read and deserialization pass.

  2. Magic Constants: I extracted the visualization colors and dimensions into named constants (e.g., COLOR_COMMAND, TIMELINE_HEIGHT) at the top of src/bin/gui.rs to improve maintainability and readability.

google-labs-jules Bot and others added 2 commits February 16, 2026 12:02
- Refactored `ReplayEngine` to expose `calculate_summary` as a static method, allowing session summary calculation from already loaded events.
- Updated `src/bin/gui.rs` to compute session summary from in-memory events instead of re-reading from disk, improving performance for large sessions.
- Extracted magic constants for timeline colors and dimensions into named constants in `src/bin/gui.rs` for better maintainability.

Co-authored-by: nur-srijan <198181700+nur-srijan@users.noreply.github.com>
Comment thread src/bin/gui.rs
sessions: Vec<timeloop_terminal::session::Session>,
selected: Option<String>,
replay_summary: Option<timeloop_terminal::replay::ReplaySummary>,
replay_events: Vec<Event>,

Check failure

Code scanning / clippy

cannot find type Event in this scope Error

cannot find type Event in this scope
Comment thread src/bin/gui.rs
let x = rect.min.x + t * rect.width();

let (color, height_fraction, y_offset) = match event.event_type {
EventType::Command { .. } => (COLOR_COMMAND, 0.8, 0.1),

Check failure

Code scanning / clippy

failed to resolve: use of undeclared type EventType Error

failed to resolve: use of undeclared type EventType
Comment thread src/bin/gui.rs

let (color, height_fraction, y_offset) = match event.event_type {
EventType::Command { .. } => (COLOR_COMMAND, 0.8, 0.1),
EventType::FileChange { .. } => (COLOR_FILE_CHANGE, 0.8, 0.1),

Check failure

Code scanning / clippy

failed to resolve: use of undeclared type EventType Error

failed to resolve: use of undeclared type EventType
Comment thread src/bin/gui.rs
let (color, height_fraction, y_offset) = match event.event_type {
EventType::Command { .. } => (COLOR_COMMAND, 0.8, 0.1),
EventType::FileChange { .. } => (COLOR_FILE_CHANGE, 0.8, 0.1),
EventType::TerminalState { .. } => (COLOR_TERMINAL, 0.4, 0.3),

Check failure

Code scanning / clippy

failed to resolve: use of undeclared type EventType Error

failed to resolve: use of undeclared type EventType
Comment thread src/bin/gui.rs
EventType::Command { .. } => (COLOR_COMMAND, 0.8, 0.1),
EventType::FileChange { .. } => (COLOR_FILE_CHANGE, 0.8, 0.1),
EventType::TerminalState { .. } => (COLOR_TERMINAL, 0.4, 0.3),
EventType::KeyPress { .. } => (COLOR_KEYPRESS, 0.2, 0.4),

Check failure

Code scanning / clippy

failed to resolve: use of undeclared type EventType Error

failed to resolve: use of undeclared type EventType
Comment thread src/bin/gui.rs
EventType::FileChange { .. } => (COLOR_FILE_CHANGE, 0.8, 0.1),
EventType::TerminalState { .. } => (COLOR_TERMINAL, 0.4, 0.3),
EventType::KeyPress { .. } => (COLOR_KEYPRESS, 0.2, 0.4),
EventType::SessionMetadata { .. } => (COLOR_METADATA, 0.5, 0.25),

Check failure

Code scanning / clippy

failed to resolve: use of undeclared type EventType Error

failed to resolve: use of undeclared type EventType
Comment thread src/bin/gui.rs
let y_end = y_start + rect.height() * height_fraction;

// Use thinner lines for keypresses to avoid clutter
let stroke_width = if matches!(event.event_type, EventType::KeyPress { .. }) {

Check failure

Code scanning / clippy

failed to resolve: use of undeclared type EventType Error

failed to resolve: use of undeclared type EventType
Comment thread src/main.rs Fixed
Comment thread src/main.rs Fixed
Updated the summarize_session call to include api_key_opt.
Comment thread src/main.rs
async fn run_ai_summarize(session_id: &str, model: Option<&str>) -> Result<(), TimeLoopError> {
let model = model.unwrap_or("openrouter/auto");
let summary = timeloop_terminal::ai::summarize_session(session_id, model, None).await?;
let summary = timeloop_terminal::ai::summarize_session(session_id, model, api_key_opt).await?;

Check failure

Code scanning / clippy

cannot find value api_key_opt in this scope Error

cannot find value api_key_opt in this scope
Comment thread src/storage.rs
let _ = Self::save_to_path(path, self);
} else if self.inner.is_none() {
let _ = Self::save_to_disk(false);
let _ = Self::save_to_disk();

Check failure

Code scanning / CodeQL

Hard-coded cryptographic value Critical

This hard-coded value is used as
a salt
.

Copilot Autofix

AI about 1 month ago

General fix: Avoid initializing cryptographic salts with hard‑coded constants. Instead, allocate the buffer directly using a CSPRNG so that no constant value is ever associated with the salt variable, even transiently.

Best targeted fix here: Replace the two‑step pattern

let mut salt = vec![0u8; 16];
let mut osrng = rand::rngs::OsRng;
osrng.fill_bytes(&mut salt);

with a single expression that generates the salt from random bytes, e.g. using rand::RngCore::next_u32 in a small loop or, more simply, using rand::Rng’s gen::<[u8; 16]>() (if available in this file), then collecting into a Vec<u8>. Since we cannot assume traits are already imported, a minimal, explicit approach that only uses OsRng and standard library methods is to allocate an uninitialized [u8; 16] array, fill it via fill_bytes, and convert it to Vec<u8>. This keeps the same salt length and preserves the existing call to derive_key_with_params, so functionality (random salt, same parameters) remains unchanged.

Concretely, in src/storage.rs, in the block starting at line 398 where a new salt is generated, change the code around line 400–405 to allocate a [u8; 16] array, fill it with OsRng, then convert it into a Vec<u8> that is passed to derive_key_with_params and stored in encryption_salt. No new external dependencies are required; we can continue using rand::rngs::OsRng and fill_bytes as before.


Suggested changeset 1
src/storage.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/storage.rs b/src/storage.rs
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -397,9 +397,10 @@
 
         // If file didn't exist or wasn't encrypted, generate a salt now
         if encryption_salt.is_none() {
-            let mut salt = vec![0u8; 16];
             let mut osrng = rand::rngs::OsRng;
-            osrng.fill_bytes(&mut salt);
+            let mut salt_array = [0u8; 16];
+            osrng.fill_bytes(&mut salt_array);
+            let salt = salt_array.to_vec();
             let key = Self::derive_key_with_params(passphrase, &salt, Some(params));
             encryption_key = Some(key);
             encryption_salt = Some(salt);
EOF
@@ -397,9 +397,10 @@

// If file didn't exist or wasn't encrypted, generate a salt now
if encryption_salt.is_none() {
let mut salt = vec![0u8; 16];
let mut osrng = rand::rngs::OsRng;
osrng.fill_bytes(&mut salt);
let mut salt_array = [0u8; 16];
osrng.fill_bytes(&mut salt_array);
let salt = salt_array.to_vec();
let key = Self::derive_key_with_params(passphrase, &salt, Some(params));
encryption_key = Some(key);
encryption_salt = Some(salt);
Copilot is powered by AI and may make mistakes. Always verify output.
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.

2 participants