Skip to content

fix(scan): recent-scan strip survives page navigation - #17

Merged
DysektAI merged 4 commits into
masterfrom
fix/scan-recents-persist
Jul 27, 2026
Merged

fix(scan): recent-scan strip survives page navigation#17
DysektAI merged 4 commits into
masterfrom
fix/scan-recents-persist

Conversation

@DysektAI

@DysektAI DysektAI commented Jul 24, 2026

Copy link
Copy Markdown
Member

Root cause

Index.razor kept the recents list (_recent) as component-local state. Navigating to History/Settings/Credits disposes the Scan page component; on return, the list was re-seeded from MenuVM.LastItemScan only — so users saw just the latest scan (#12).

Fix

The strip now derives from SessionHistoryService — a DI singleton that already records every scan for the session (and backs the History page). No state lives in the component anymore:

  • RecentScans = first 5 entries of History.Items
  • SelectRecent reuses the stored historical ScanResultViewModel directly (same data ScanResultAdapter.Map(..., isHistorical: true) produced before)
  • Component subscribes to History.Changed for re-render, unsubscribes in Dispose

Validation

  • Build + full test suite (205 passed), csharpier clean.
  • Manual smoke: scan several items → History tab → back to Scan → all recents still present; selecting one shows the historical result.

Fixes #12


Open in Devin Review

Summary by cubic

Persist the Recent Scans strip across navigation by sourcing it from SessionHistoryService instead of component-local state. Shows the first 5 session scans; selecting a recent uses the stored ScanResultViewModel.

  • Bug Fixes
    • Derived RecentScans from History.Items and snapshot per render to avoid double evaluation and extra allocations.
    • Subscribed to History.Changed for re-render; unsubscribed in Dispose.
    • Removed local seeding and AddRecent; SelectRecent assigns the historical view model; thumbnails use Item.ImageUrl.
    • Added tests ensuring history is bounded to 50 entries and duplicate items move to the front.

Written for commit 3e11aad. Summary will update on new commits.

Review in cubic

Greptile Summary

Fixes the recent-scan strip being wiped on page navigation (#12) by sourcing it from the SessionHistoryService singleton instead of component-local state. The component now subscribes to History.Changed for re-renders and unsubscribes in Dispose.

  • RecentScans computes the first 5 entries from History.Items; SelectRecent assigns the stored ScanResultViewModel directly instead of re-mapping via ScanResultAdapter, so clicked recents now reflect scan-time pricing (matching History page behavior).
  • AddRecent and the _recent field are removed; lifecycle wiring is moved to OnInitialized/Dispose with correct event unsubscription.

Confidence Score: 4/5

Safe to merge. The change is a focused, single-file fix with no new dependencies; event subscription and disposal are wired correctly.

The fix is straightforward and correctly solves the stated problem. The only finding is that RecentScans is evaluated twice per render, causing two lock acquisitions and four allocations per cycle — a minor inefficiency with no behavioural impact given that history grows monotonically within a session.

Files Needing Attention: No files require special attention; the single changed file is self-contained.

Important Files Changed

Filename Overview
src/App/Pages/App/Index.razor Migrates recent-scan strip from component-local state to the DI singleton SessionHistoryService; removes AddRecent/SelectRecent(ItemScan) in favour of the stored ScanResultViewModel; properly subscribes/unsubscribes History.Changed in OnInitialized/Dispose. Minor double-evaluation of the RecentScans property per render.

Sequence Diagram

sequenceDiagram
    participant ScanThread as Scan Thread
    participant History as SessionHistoryService
    participant Index as Index.razor
    participant Renderer as Blazor Renderer

    ScanThread->>History: ItemQueue.Changed fires
    History->>History: Record(ScanResultViewModel)
    History-->>Index: History.Changed event
    Index->>Renderer: InvokeAsync(StateHasChanged)
    Renderer->>Index: Re-render
    Index->>History: RecentScans → History.Items.Take(5)
    History-->>Index: "IReadOnlyList<ScanResultViewModel>"
    Index->>Renderer: Display recent strip

    note over Index: User navigates away
    Index->>Index: Dispose() — unsubscribe History.Changed
    note over History: Items survive (singleton)

    note over Index: User navigates back
    Index->>History: OnInitialized — subscribe History.Changed
    Index->>History: RecentScans → History.Items.Take(5)
    History-->>Index: All session scans still present
Loading

Comments Outside Diff (1)

  1. src/App/Pages/App/Index.razor, line 48-62 (link)

    P2 RecentScans property evaluated twice per render

    RecentScans is a computed property backed by History.Items, which internally acquires a lock and allocates a full copy of the history list (up to 50 entries) before .Take(5).ToArray() produces a second allocation. The Razor template evaluates the property twice per render cycle — once for the .Count > 0 guard and again for the @foreach — resulting in two lock acquisitions and four heap allocations. Snapshotting the result into a cached field (refreshed in OnHistoryChanged and OnInitialized) would eliminate the redundancy and also guarantee the if-guard and the foreach see the same snapshot.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/App/Pages/App/Index.razor
    Line: 48-62
    
    Comment:
    `RecentScans` property evaluated twice per render
    
    `RecentScans` is a computed property backed by `History.Items`, which internally acquires a lock and allocates a full copy of the history list (up to 50 entries) before `.Take(5).ToArray()` produces a second allocation. The Razor template evaluates the property twice per render cycle — once for the `.Count > 0` guard and again for the `@foreach` — resulting in two lock acquisitions and four heap allocations. Snapshotting the result into a cached field (refreshed in `OnHistoryChanged` and `OnInitialized`) would eliminate the redundancy and also guarantee the `if`-guard and the `foreach` see the same snapshot.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/App/Pages/App/Index.razor:48-62
`RecentScans` property evaluated twice per render

`RecentScans` is a computed property backed by `History.Items`, which internally acquires a lock and allocates a full copy of the history list (up to 50 entries) before `.Take(5).ToArray()` produces a second allocation. The Razor template evaluates the property twice per render cycle — once for the `.Count > 0` guard and again for the `@foreach` — resulting in two lock acquisitions and four heap allocations. Snapshotting the result into a cached field (refreshed in `OnHistoryChanged` and `OnInitialized`) would eliminate the redundancy and also guarantee the `if`-guard and the `foreach` see the same snapshot.

Reviews (1): Last reviewed commit: "fix(scan): keep recent-scan strip across..." | Re-trigger Greptile

The recents strip lived in component-local state on the Scan page, so
navigating to any other tab disposed the component and wiped everything
but the latest scan (re-seeded from MenuVM on return).

The strip now derives from the DI-singleton SessionHistoryService (the
same store backing the History page), which already records every scan
for the session. Selecting a recent entry reuses the stored historical
view model directly.

Fixes #12
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 538312b0-3d03-40cc-b1ce-fa0a0db10f3e

📥 Commits

Reviewing files that changed from the base of the PR and between 5e17419 and 3e11aad.

📒 Files selected for processing (2)
  • src/App/Pages/App/Index.razor
  • tests/RatScanner.Tests/PresentationServicesTests.cs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/scan-recents-persist

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

@codeant-ai

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 potential issues.

Open in Devin Review

Comment on lines +469 to 474
private void SelectRecent(ScanResultViewModel scan)
{
_result = ScanResultAdapter.Map(scan, MenuVM, true);
_result = scan;
_detailsOpen = false;
_copiedId = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: SelectRecent now shows a snapshot rather than freshly re-mapped data

Previously SelectRecent re-mapped from the live ItemScan via ScanResultAdapter.Map(scan, MenuVM, true), recomputing quest/hideout requirements and pricing against the current MenuVM/TarkovTracker state at click time. Now it reuses the ScanResultViewModel snapshot captured at scan time by SessionHistoryService.Record (src/App/Presentation/SessionHistoryService.cs:43-54). If quest/hideout progress or prices change during a session, clicking a recent thumbnail will show stale requirement/price data. This is consistent with the History page behavior and appears intended per the PR description, so it is not flagged as a bug, but reviewers should confirm the desired semantics.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/App/Pages/App/Index.razor Outdated
// The recent-scans strip derives from the DI-singleton SessionHistoryService
// instead of component-local state, so navigating away from the Scan page
// and back no longer wipes it (issue #12).
private IReadOnlyList<ScanResultViewModel> RecentScans => History.Items.Take(RecentLimit).ToArray();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: RecentScans property takes two independent snapshots per render

RecentScans (src/App/Pages/App/Index.razor:437) locks and copies History.Items each time it is evaluated. In the markup it is evaluated separately in @if (RecentScans.Count > 0) and @foreach (... in RecentScans), so two independent snapshots are taken per render. A concurrent scan landing between the two evaluations could theoretically produce a brief inconsistency (Count > 0 but different content), though it is harmless in practice. Minor allocation/locking overhead only.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@kilo-code-bot

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

@greptile-apps greptile-apps 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.

DysektAI has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

DysektAI has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

DysektAI has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@DysektAI
DysektAI merged commit b77bc6e into master Jul 27, 2026
15 checks passed
@DysektAI
DysektAI deleted the fix/scan-recents-persist branch July 27, 2026 07:04
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.

Scan history resetting when switching to other tab

1 participant