feat: add Mission Intelligence & Operations Logbook - #173
Conversation
🤖 CodeAnt AI — Review Status
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
📝 WalkthroughWalkthroughThe PR adds a centralized Zustand logbook with typed entries, animated rendering, filtering, scrolling, unread tracking, and event capture across mission initialization, commands, searches, satellite selection, camera actions, and collision alerts. ChangesMission Operations Logbook
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant MissionUI
participant logEvent
participant useLogbookStore
participant LogbookPanel
MissionUI->>logEvent: Record mission event
logEvent->>useLogbookStore: Add timestamped entry
LogbookPanel->>useLogbookStore: Read entries
useLogbookStore-->>LogbookPanel: Return entries and unread state
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment Warning |
| useEffect(() => { | ||
| const prevCount = prevCountRef.current; | ||
| const grew = entries.length > prevCount; | ||
| prevCountRef.current = entries.length; | ||
|
|
||
| if (pinnedToTop && grew) { | ||
| listRef.current?.scrollTo({ top: 0, behavior: 'smooth' }); | ||
| } | ||
|
|
||
| // Surface a "N new entries" affordance when entries arrive while the | ||
| // user is reading history, instead of yanking their scroll position. | ||
| setNewSinceScroll((n) => (pinnedToTop ? 0 : grew ? n + (entries.length - prevCount) : n)); | ||
| }, [entries.length, pinnedToTop]); |
There was a problem hiding this comment.
Suggestion: The new-entry detector relies only on entries.length. Once the store reaches its 300-entry cap, adding an entry replaces an old entry without changing the length, so this effect does not run and neither auto-scrolls to the latest entry nor increments the new-entry indicator. Track a monotonically increasing event/version value or compare the newest entry ID instead of only the array length. [state/lifecycle]
Severity Level: Major ⚠️
- ❌ New entries beyond 300 do not trigger latest-entry auto-scroll.
- ⚠️ History readers miss the “N new entries” indicator.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/components/Logbook/LogbookPanel.tsx
**Line:** 66:78
**Comment:**
*State Lifecycle: The new-entry detector relies only on `entries.length`. Once the store reaches its 300-entry cap, adding an entry replaces an old entry without changing the length, so this effect does not run and neither auto-scrolls to the latest entry nor increments the new-entry indicator. Track a monotonically increasing event/version value or compare the newest entry ID instead of only the array length.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| }, | ||
| duration: 1.5, | ||
| }); | ||
| logEvent('TRACKING', 'MEDIUM', 'ISS tracking engaged', 'Camera locked onto the International Space Station.'); |
There was a problem hiding this comment.
Suggestion: The ISS action performs a camera fly-to, but records the event under TRACKING. This places the event in the wrong category and makes camera activity invisible when users filter the logbook by CAMERA; record it as a camera event like the other fly-to actions. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ ISS camera actions disappear from CAMERA filtering.
- ⚠️ Logbook categories become inconsistent across fly-to actions.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/components/EarthTwin.tsx
**Line:** 198:198
**Comment:**
*Api Mismatch: The ISS action performs a camera fly-to, but records the event under `TRACKING`. This places the event in the wrong category and makes camera activity invisible when users filter the logbook by `CAMERA`; record it as a camera event like the other fly-to actions.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| (conj.risk_level === 'CRITICAL' || conj.risk_level === 'HIGH') && | ||
| !loggedCollisionIdsRef.current.has(conj.id) | ||
| ) { |
There was a problem hiding this comment.
Suggestion: The deduplication key contains only conj.id, so an already-logged HIGH conjunction that is later promoted to CRITICAL will never generate a CRITICAL alert entry. Include the risk level in the deduplication state, or explicitly log severity changes. [logic error]
Severity Level: Major ⚠️
- ⚠️ Risk escalation is missing from the Alert log.
- ❌ Operators may not see newly critical conjunctions.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** frontend/src/pages/Dashboard.tsx
**Line:** 121:123
**Comment:**
*Logic Error: The deduplication key contains only `conj.id`, so an already-logged HIGH conjunction that is later promoted to CRITICAL will never generate a CRITICAL alert entry. Include the risk level in the deduplication state, or explicitly log severity changes.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@frontend/src/components/layouts/MainLayout.tsx`:
- Around line 63-86: Update the unread-log tracking in MainLayout around
logbookEntryCount and lastViewedLogCountRef to use a monotonic insertion
revision or sequence exposed by logbookStore instead of entries.length. Store
and compare the last viewed revision, while preserving the existing
reset-to-zero behavior when the Logs tab is open and calculating unread entries
from the revision difference.
In `@frontend/src/components/Logbook/LogbookPanel.tsx`:
- Around line 66-78: Update the LogbookPanel useEffect to detect additions by
comparing the current newest entry ID or entry IDs with the previous render,
rather than relying only on entries.length, so replacements at the 300-entry cap
still scroll or increment the new-entry affordance. Also reset newSinceScroll
when the entries list is cleared, while preserving the existing pinnedToTop
behavior.
In `@frontend/src/pages/Dashboard.tsx`:
- Around line 117-134: Move the conjunction alert deduplication state out of
Dashboard’s loggedCollisionIdsRef and into logbookStore or another
session-scoped bounded deduplication index. Update the index atomically when
creating the log entry so remounting Dashboard does not re-log existing HIGH or
CRITICAL conjunctions, while preserving the current risk filtering and alert
payload.
In `@frontend/src/pages/Satellites.tsx`:
- Around line 60-66: Add an unmount cleanup in the Satellites component that
clears searchTimer.current, ensuring the pending setTimeout callback cannot run
after the page is left while preserving the existing debounced search behavior.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 67fca6a3-6267-42d1-979e-ffc89ed55586
📒 Files selected for processing (11)
frontend/src/components/EarthTwin.tsxfrontend/src/components/Logbook/LogEntryItem.tsxfrontend/src/components/Logbook/LogbookPanel.tsxfrontend/src/components/Logbook/logbookConfig.tsfrontend/src/components/layouts/MainLayout.tsxfrontend/src/hooks/useSatelliteSelection.tsfrontend/src/pages/Dashboard.tsxfrontend/src/pages/Satellites.tsxfrontend/src/store/logbookStore.tsfrontend/src/store/uiStore.tsfrontend/src/types/logbook.ts
| const logbookEntryCount = useLogbookStore((s) => s.entries.length); | ||
| const lastViewedLogCountRef = useRef(0); | ||
| const [unreadLogCount, setUnreadLogCount] = useState(0); | ||
| const isViewingLogs = rightDrawerOpen && activeDrawerTab === 'LOGS'; | ||
|
|
||
| // Record a System event once, when mission control first comes online. | ||
| // Module-scope guard (not a ref) so it survives React StrictMode's | ||
| // double-invoke in dev *and* Vite HMR module reloads — a component-local | ||
| // ref resets on remount, but this only resets on a genuine full page load. | ||
| useEffect(() => { | ||
| if (hasLoggedMissionInit) return; | ||
| hasLoggedMissionInit = true; | ||
| logEvent('SYSTEM', 'LOW', 'Mission control interface initialized', 'Dashboard shell mounted and ready.'); | ||
| }, []); | ||
|
|
||
| // Sync the unread badge to the logbook store (an external system) as new | ||
| // entries arrive or the Logs tab is opened; see the identical, pre-existing | ||
| // pattern in EarthTwin.tsx for setState-in-effect used this way. | ||
| useEffect(() => { | ||
| if (isViewingLogs) { | ||
| lastViewedLogCountRef.current = logbookEntryCount; | ||
| } | ||
| setUnreadLogCount(isViewingLogs ? 0 : Math.max(0, logbookEntryCount - lastViewedLogCountRef.current)); | ||
| }, [isViewingLogs, logbookEntryCount]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Track unread entries independently of the capped array length.
When the logbook reaches its cap, a new entry replaces an old entry but entries.length does not change. This selector does not update, so the unread badge stops increasing for later events.
Expose a monotonic insertion revision or sequence from logbookStore. Store the last viewed revision instead of the entry count.
🤖 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 `@frontend/src/components/layouts/MainLayout.tsx` around lines 63 - 86, Update
the unread-log tracking in MainLayout around logbookEntryCount and
lastViewedLogCountRef to use a monotonic insertion revision or sequence exposed
by logbookStore instead of entries.length. Store and compare the last viewed
revision, while preserving the existing reset-to-zero behavior when the Logs tab
is open and calculating unread entries from the revision difference.
| useEffect(() => { | ||
| const prevCount = prevCountRef.current; | ||
| const grew = entries.length > prevCount; | ||
| prevCountRef.current = entries.length; | ||
|
|
||
| if (pinnedToTop && grew) { | ||
| listRef.current?.scrollTo({ top: 0, behavior: 'smooth' }); | ||
| } | ||
|
|
||
| // Surface a "N new entries" affordance when entries arrive while the | ||
| // user is reading history, instead of yanking their scroll position. | ||
| setNewSinceScroll((n) => (pinnedToTop ? 0 : grew ? n + (entries.length - prevCount) : n)); | ||
| }, [entries.length, pinnedToTop]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Detect new entries by identity, not only by list length.
After the store reaches 300 entries, each new entry replaces an old entry and keeps entries.length unchanged. Line 68 then treats the update as not new. The panel does not auto-scroll or show the new-entry control.
If clearAll() runs while the user is not pinned to the top, line 77 also retains a stale new-entry count.
Track the previous newest entry ID or previous entry IDs. Reset newSinceScroll when the list is cleared.
🤖 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 `@frontend/src/components/Logbook/LogbookPanel.tsx` around lines 66 - 78,
Update the LogbookPanel useEffect to detect additions by comparing the current
newest entry ID or entry IDs with the previous render, rather than relying only
on entries.length, so replacements at the 300-entry cap still scroll or
increment the new-entry affordance. Also reset newSinceScroll when the entries
list is cleared, while preserving the existing pinnedToTop behavior.
| const loggedCollisionIdsRef = useRef<Set<number>>(new Set()); | ||
| useEffect(() => { | ||
| for (const conj of conjunctions) { | ||
| if ( | ||
| (conj.risk_level === 'CRITICAL' || conj.risk_level === 'HIGH') && | ||
| !loggedCollisionIdsRef.current.has(conj.id) | ||
| ) { | ||
| loggedCollisionIdsRef.current.add(conj.id); | ||
| logEvent( | ||
| 'ALERTS', | ||
| conj.risk_level === 'CRITICAL' ? 'CRITICAL' : 'HIGH', | ||
| 'Conjunction risk detected', | ||
| `${conj.object_a?.name ?? 'Unknown'} vs ${conj.object_b?.name ?? 'Unknown'} — ${(conj.probability * 100).toFixed(2)}% probability`, | ||
| { RISK_LEVEL: conj.risk_level, MISS_DISTANCE_M: conj.miss_distance_m.toFixed(0) } | ||
| ); | ||
| } | ||
| } | ||
| }, [conjunctions]); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Persist conjunction deduplication outside Dashboard.
Navigating away from /dashboard resets loggedCollisionIdsRef. When the page remounts, cached HIGH and CRITICAL conjunctions are logged again as new alerts.
Move the deduplication key into logbookStore, or use a bounded event-deduplication index that persists for the application session and is updated atomically with the log entry.
🤖 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 `@frontend/src/pages/Dashboard.tsx` around lines 117 - 134, Move the
conjunction alert deduplication state out of Dashboard’s loggedCollisionIdsRef
and into logbookStore or another session-scoped bounded deduplication index.
Update the index atomically when creating the log entry so remounting Dashboard
does not re-log existing HIGH or CRITICAL conjunctions, while preserving the
current risk filtering and alert payload.
| searchTimer.current = setTimeout(() => { | ||
| setDebounced(val); | ||
| setPage(1); | ||
| if (val.trim()) { | ||
| logEvent('SEARCH', 'LOW', 'Satellite catalog search', `Query: "${val.trim()}"`); | ||
| } | ||
| }, 400); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Cancel the pending search timer on unmount.
If the user leaves this page within 400 ms, the callback still records a search that did not execute. Clear searchTimer.current in an unmount cleanup.
Proposed fix
-import React, { useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
...
const searchTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+ useEffect(() => () => {
+ if (searchTimer.current) clearTimeout(searchTimer.current);
+ }, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| searchTimer.current = setTimeout(() => { | |
| setDebounced(val); | |
| setPage(1); | |
| if (val.trim()) { | |
| logEvent('SEARCH', 'LOW', 'Satellite catalog search', `Query: "${val.trim()}"`); | |
| } | |
| }, 400); | |
| searchTimer.current = setTimeout(() => { | |
| setDebounced(val); | |
| setPage(1); | |
| if (val.trim()) { | |
| logEvent('SEARCH', 'LOW', 'Satellite catalog search', `Query: "${val.trim()}"`); | |
| } | |
| }, 400); | |
| useEffect(() => () => { | |
| if (searchTimer.current) clearTimeout(searchTimer.current); | |
| }, []); |
🤖 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 `@frontend/src/pages/Satellites.tsx` around lines 60 - 66, Add an unmount
cleanup in the Satellites component that clears searchTimer.current, ensuring
the pending setTimeout callback cannot run after the page is left while
preserving the existing debounced search behavior.
User description
Summary
Implements the Mission Intelligence & Operations Logbook — a real-time, categorized, filterable audit trail of significant user interactions and system events, replacing the previous static/hardcoded "LOGS" tab in the right-side drawer.
Adds:
logbookStore(capped at 300 entries) plus alogEvent()helper callable from anywhere in the app without subscribing to the storeLogbookPanelUI: search box, category filter chips (Tracking/Camera/Search/Alerts/System/Mission), priority filter chips (Low/Medium/High/Critical), animated expandable entries, and a smart auto-scroll that shows a "N new entries ↑" indicator instead of yanking the user's scroll position while they're reviewing historyWires real logging into existing interactions:
Related Issue
Fixes #161
Type of Change
Screenshots / Screen Recordings
Testing Performed
Details:
npx tsc -b --noEmit— clean, no errorsnpm run lint— zero issues in any file this PR touches (verified by diffing against the pre-existing baseline, which already had unrelated lint errors in files outside this PR)Breaking Changes
None. This is additive — no existing props, routes, or stored data shapes changed. The previous hardcoded LOGS tab content is replaced with live data, but that tab had no persisted state to migrate.
Checklist
ECSoC26 Submission
ECSoC26-L1– BeginnerECSoC26-L2– IntermediateCodeAnt-AI Description
Add a searchable mission operations logbook
What Changed
Impact
✅ Searchable mission history✅ Clearer high-risk collision alerts✅ Unread event visibility💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit