Skip to content

feat: tab cleanup, context menu, settings dialog, event log workspace - #79

Merged
adamgell merged 29 commits into
mainfrom
feature/multi-feature-batch
Apr 2, 2026
Merged

feat: tab cleanup, context menu, settings dialog, event log workspace#79
adamgell merged 29 commits into
mainfrom
feature/multi-feature-batch

Conversation

@adamgell

@adamgell adamgell commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary

Four features in one batch:

1. Tab Close Cleanup (#55)

  • Clears log content and filters when the last tab is closed
  • Fix applied directly in the closeTab store action — no reactive useEffect

2. Right-Click Context Menu (#53)

  • Native OS context menu on log rows via Tauri v2 Menu API
  • 10 menu items: Copy Line, Copy Message, Copy Timestamp, Include Filter, Exclude Filter, Jump to Line, Error Lookup (with pre-populated code), Open Source File (reveals in OS file manager)
  • New addQuickFilter action in filter store for instant filter application
  • New reveal_in_file_manager Rust command (cross-platform: Explorer, Finder, xdg-open)

3. Settings Dialog (#51)

  • Replaces AccessibilityDialog with a 5-tab Settings dialog (Ctrl+,)
  • Appearance: Theme selector (8 themes), font family, font sizes, live preview
  • Columns: Column visibility toggles with checkboxes
  • Behavior: Default info pane state, confirm before tab close
  • Updates: Auto-update toggle, version display, skipped version management
  • File Associations: Windows-only .log file association
  • All settings apply immediately, persist across sessions

4. Event Log Workspace (#77, #71)

  • New workspace for viewing Windows Event Log (.evtx) files
  • Backend: Rust module with evtx crate parser, cross-file timestamp sorting, live query stubs for Windows
  • Frontend: Full workspace with SourcePicker, ChannelPicker, virtual-scrolled timeline, detail pane, filter bar
  • Supports loading multiple .evtx files with cross-channel unified timeline
  • "This Computer" button (Windows only) for live log enumeration
  • Level/EventID/search filtering, sortable columns

Test plan

  • cargo test — all tests pass
  • cargo clippy -- -D warnings — zero warnings
  • npx tsc --noEmit — clean
  • npm run frontend:build — builds successfully
  • Manual: close last tab → log view clears
  • Manual: right-click log row → context menu with all items
  • Manual: Settings dialog opens with Ctrl+, → all tabs functional
  • Manual: open .evtx file → events display in Event Log workspace

🤖 Generated with Claude Code

adamgell and others added 27 commits April 1, 2026 20:27
… log workspace

Four feature specs for upcoming work:
- Tab close cleanup (#55): bug fix for stale content on tab close
- Right-click context menu (#53): native OS menu with quick filters
- Settings dialog (#51): consolidated preferences with update toggle
- Event Log workspace (#77/#71): live + file-based EVTX viewer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Tab close cleanup (#55): fix stale content on tab close
- Right-click context menu (#53): native OS menu with quick filters
- Settings dialog (#51): consolidated preferences with 5 tabs
- Event Log workspace (#77/#71): EVTX viewer with live + file support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…menu item

Wire up error code from context menu to ErrorLookupDialog via lookupErrorCode
store field so the dialog immediately searches on open. Rename "Open Source File"
to "Copy Source Path" since no reveal-in-finder API is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Tauri backend command `reveal_in_file_manager` that opens Explorer/Finder/xdg-open with the file selected, and wires it to the context menu replacing "Copy Source Path" with "Open Source File".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap window.prompt in setTimeout(..., 0) in use-context-menu.ts so it
runs after the native Tauri menu closes, preventing the prompt from
being swallowed. Fix reveal.rs to pass /select, and the path as a
single argument to explorer.exe, as required by its CLI interface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces showAccessibilityDialog with showSettingsDialog, adds
hiddenColumns, autoUpdateEnabled, defaultShowInfoPane, confirmTabClose
fields with setters and persistence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts the body content (theme selector, font family dropdown with
search/filter, font size sliders, live preview, reset button) into a
standalone tab component for the new Settings dialog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s tabs

- ColumnsTab: column visibility checkboxes using hiddenColumns from ui-store
- BehaviorTab: checkboxes for defaultShowInfoPane and confirmTabClose
- UpdatesTab: auto-update toggle, version display, skipped version with clear
- FileAssociationsTab: Windows-only .log file association registration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tabbed dialog with 5 tabs (Appearance, Columns, Behavior, Updates,
File Associations). File Associations tab is hidden on non-Windows.
Deletes AccessibilityDialog.tsx.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…handlers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AppearanceTab: adds theme <select> dropdown at top, reads themeId from ui-store and calls setThemeId on change for immediate application
- ColumnsTab: adds Reset Columns button at bottom calling resetColumns()
- ui-store: extends resetColumns() to also clear hiddenColumns so visibility state is fully reset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…types

- Filter hiddenColumns from visibleColumns in LogListView so the
  Columns settings tab actually hides/shows columns in the log view
- Add window.confirm prompt in closeTab when confirmTabClose is enabled
- Set showInfoPane to defaultShowInfoPane when opening a new tab
- Add type="button" to all <button> elements in settings dialog and tabs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 2, 2026 00:28

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

Pull request overview

This PR delivers a batch of UI/UX and workspace features across the React frontend and Tauri (Rust) backend: tab-close state cleanup, a native right-click context menu for log rows, a new multi-tab Settings dialog, and a new Event Log Viewer workspace (EVTX parsing + initial live-log scaffolding).

Changes:

  • Clear log + filter state when the last tab is closed; add optional “confirm tab close”.
  • Add native OS context menu on log rows (copy actions, quick filters, jump-to-line, error lookup, reveal source file).
  • Introduce Settings dialog (appearance/columns/behavior/updates/file associations) and add Event Log Viewer workspace with EVTX parsing/store/UI.

Reviewed changes

Copilot reviewed 48 out of 49 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/types/log.ts Adds "event-log" to WorkspaceId.
src/types/event-log-workspace.ts Introduces TS types for EVTX records/channels/parse result.
src/stores/ui-store.ts Adds settings state (hidden columns, updates toggle, defaults), settings dialog wiring, tab-close cleanup.
src/stores/filter-store.ts Adds addQuickFilter for context-menu filtering.
src/stores/evtx-store.ts New Zustand store for EVTX data, filters, sorting, selection, and Tauri invokes.
src/hooks/use-update-checker.ts Respects autoUpdateEnabled by skipping startup update checks.
src/hooks/use-keyboard.ts Renames accessibility dialog references to settings dialog state.
src/hooks/use-context-menu.ts New hook building a native context menu via Tauri v2 Menu API.
src/hooks/use-app-menu.ts Renames menu action handling from accessibility → settings.
src/components/log-view/LogRow.tsx Adds onContextMenu support on log rows.
src/components/log-view/LogListView.tsx Wires context menu handler; applies hidden-columns filtering.
src/components/layout/Toolbar.tsx Adds Event Log Viewer workspace label; settings action rename.
src/components/layout/AppShell.tsx Routes "event-log" workspace and swaps AccessibilityDialog → SettingsDialog.
src/components/event-log-workspace/SourcePicker.tsx Entry UI for EVTX files vs live channels (Windows-only button).
src/components/event-log-workspace/EvtxTimelineRow.tsx Event row UI for EVTX timeline with keyboard selection.
src/components/event-log-workspace/EvtxTimeline.tsx Virtualized EVTX timeline with filtering + sorting.
src/components/event-log-workspace/EvtxFilterBar.tsx Level toggles, EventID filter, search, sort UI.
src/components/event-log-workspace/EvtxDetailPane.tsx Selected event details, event data table, “raw XML” display.
src/components/event-log-workspace/EventLogWorkspace.tsx Main EVTX workspace composition (picker → filters/timeline/details).
src/components/event-log-workspace/ChannelPicker.tsx Channel selection sidebar with search + select/deselect all.
src/components/dialogs/SettingsDialog.tsx New tabbed settings dialog shell + focus trapping.
src/components/dialogs/settings/UpdatesTab.tsx Adds auto-update toggle + version display + skipped-version management.
src/components/dialogs/settings/FileAssociationsTab.tsx Windows-only file association action (invokes backend command).
src/components/dialogs/settings/ColumnsTab.tsx Column visibility toggles + reset columns.
src/components/dialogs/settings/BehaviorTab.tsx Default info pane + confirm tab close settings.
src/components/dialogs/settings/AppearanceTab.tsx Theme/font/font-size controls + preview; loads system fonts via Tauri.
src/components/dialogs/FilterDialog.tsx Exports emptyClause() for reuse.
src/components/dialogs/ErrorLookupDialog.tsx Supports pre-populating query via lookupErrorCode store field.
src/components/dialogs/AccessibilityDialog.tsx Removed (replaced by SettingsDialog).
src-tauri/src/menu.rs Renames menu item to “Settings…” and adds CmdOrCtrl+, accelerator.
src-tauri/src/lib.rs Registers reveal command + event-log commands under feature flag.
src-tauri/src/event_log/parser.rs New EVTX file parsing + record/channel aggregation.
src-tauri/src/event_log/models.rs New EVTX models for IPC serialization.
src-tauri/src/event_log/mod.rs Event-log module wiring (plus Windows-only live module).
src-tauri/src/event_log/live.rs Windows-only live-log scaffolding (currently stubbed).
src-tauri/src/event_log/commands.rs Tauri commands for parsing files / enumerating channels / querying channels.
src-tauri/src/commands/reveal.rs New cross-platform reveal_in_file_manager command.
src-tauri/src/commands/mod.rs Exposes new reveal command module.
src-tauri/src/commands/app_config.rs Adds "event-log" workspace when feature enabled.
src-tauri/Cargo.toml Adds event-log feature and enables it in full.
src-tauri/Cargo.lock Bumps app version entry.
docs/superpowers/specs/2026-04-01-tab-close-cleanup-design.md Design spec for tab close cleanup.
docs/superpowers/specs/2026-04-01-settings-dialog-design.md Design spec for new settings dialog.
docs/superpowers/specs/2026-04-01-event-log-workspace-design.md Design spec for EVTX workspace.
docs/superpowers/specs/2026-04-01-context-menu-design.md Design spec for context menu.
docs/superpowers/plans/2026-04-01-tab-close-cleanup.md Implementation plan for tab close cleanup.
docs/superpowers/plans/2026-04-01-settings-dialog.md Implementation plan for settings dialog.
docs/superpowers/plans/2026-04-01-event-log-workspace.md Implementation plan for EVTX workspace.
docs/superpowers/plans/2026-04-01-context-menu.md Implementation plan for context menu.

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

Comment on lines +45 to +70
const items: (MenuItem | PredefinedMenuItem)[] = [
await MenuItem.new({
id: "copy-line",
text: "Copy Line",
action: () => {
writeText(formatLine(entry));
},
}),
await MenuItem.new({
id: "copy-message",
text: "Copy Message",
action: () => {
writeText(entry.message);
},
}),
];

if (entry.timestampDisplay) {
items.push(
await MenuItem.new({
id: "copy-timestamp",
text: "Copy Timestamp",
action: () => {
writeText(entry.timestampDisplay!);
},
})

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

Context menu actions call writeText() without awaiting or handling rejections. Since writeText returns a Promise, failures will become unhandled rejections and there’s no user feedback. Consider wrapping clipboard writes in a try/catch (or void writeText(...).catch(...)) similar to other clipboard usage in the codebase (e.g., ErrorLookupDialog/use-keyboard).

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +56
// Build one EvtxChannelInfo per distinct channel string found in the records,
// so that ChannelPicker can match against r.channel values.
let mut channel_counts: std::collections::HashMap<String, u64> =
std::collections::HashMap::new();
for r in &records {
*channel_counts.entry(r.channel.clone()).or_insert(0) += 1;
}

if channel_counts.is_empty() {
// No records — still emit an entry keyed by the file basename so the
// file appears in the picker.
channels.push(EvtxChannelInfo {
name: source_label.clone(),
event_count: 0,
source_type: ChannelSourceType::File {
path: path_str.clone(),
},
});
} else {
for (channel_name, count) in channel_counts {
channels.push(EvtxChannelInfo {
name: channel_name,
event_count: count,
source_type: ChannelSourceType::File {
path: path_str.clone(),
},
});
}
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

When parsing multiple .evtx files, channels with the same name are pushed once per file, which can yield duplicate EvtxChannelInfo.name entries. The frontend ChannelPicker uses key={channel.name}, so duplicates will cause React key collisions and ambiguous counts. Consider aggregating channel counts across all files (e.g., HashMap keyed by channel name) or otherwise making channel identifiers unique (include file/source in the key).

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +114
for record_result in parser.records_json_value() {
if records.len() >= MAX_ENTRIES_PER_FILE {
log::warn!(
"event=evtx_entry_cap_reached file=\"{}\" cap={}",
path.display(),
MAX_ENTRIES_PER_FILE
);
break;
}

let record = match record_result {
Ok(r) => r,
Err(e) => {
log::warn!(
"event=evtx_record_skip file=\"{}\" error=\"{}\"",
path.display(),
e
);
continue;
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

parseErrors currently only increments on whole-file parse failures; per-record parse failures inside parse_single_file are logged but not counted. This makes EvtxParseResult.parse_errors misleading. Consider incrementing parse_errors when individual records fail to parse (or rename the field to clarify it’s file-level).

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +140
let provider = system["Provider"]["#attributes"]["Name"]
.as_str()
.unwrap_or("")
.to_string();

let channel = system["Channel"]
.as_str()
.unwrap_or("")
.to_string();

let event_id = extract_event_id(system);

let level = system["Level"].as_u64().unwrap_or(0) as u8;
let evtx_level = EvtxLevel::from_level_value(level);

let computer = system["Computer"]
.as_str()
.unwrap_or("")
.to_string();

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

Channel/provider/computer are defaulted to empty string when missing in the EVTX JSON. An empty channel name will break filtering/selection (selectedChannels Set won’t match) and yields unusable UI labels. Consider falling back to a non-empty placeholder like "Unknown" (or the file basename) for channel/provider/computer when absent.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +21
pub fn enumerate_channels() -> Result<Vec<EvtxChannelInfo>, String> {
// Implementation uses wevtapi.dll — Windows only
// EvtOpenChannelEnum, EvtNextChannelPath
Err("Live event log queries require Windows".to_string())
}

/// Query a specific Windows Event Log channel for recent events.
///
/// Uses `wevtapi.dll` functions `EvtQuery` and `EvtRender` to read
/// events from the specified channel.
pub fn query_channel(
_channel: &str,
_max_events: Option<u64>,
) -> Result<Vec<EvtxRecord>, String> {
Err("Live event log queries require Windows".to_string())

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The Windows-only live event log functions currently always return Err("Live event log queries require Windows"). Since this module is only compiled on Windows, the error message is inaccurate and the "This Computer" workflow will always fail. Consider returning a clearer "not implemented yet" error and/or disabling/hiding the UI entry until live queries are implemented.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +29
export interface EvtxChannelInfo {
name: string;
eventCount: number;
sourceType: "live" | { file: { path: string } };
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

Type mismatch: Rust ChannelSourceType is a serde enum (externally tagged by default), which will serialize as { live: null } / { file: { path: "..." } } (with camelCase rename). The TS type is currently "live" | { file: { path: string } }, which won’t match runtime payloads. Align the TS type with the actual serialized shape, or change the Rust enum serde representation (e.g., tagged/untagged) to match the intended TS API.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +75
<div style={{ flex: 1, overflowY: "auto", padding: "4px 8px" }}>
{filteredChannels.map((channel) => (
<div
key={channel.name}
style={{
display: "flex",
alignItems: "center",
gap: "4px",

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

ChannelPicker uses key={channel.name}. If the backend returns multiple entries with the same channel name (e.g., same channel across multiple files), this will produce duplicate React keys and unstable rendering. Consider using a composite key (name + sourceType/path) or ensuring the backend deduplicates channels before returning.

Copilot uses AI. Check for mistakes.
Comment on lines +182 to +224
{/* Tab bar */}
<div
role="tablist"
style={{
display: "flex",
gap: "0",
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
marginBottom: "14px",
}}
>
{visibleTabs.map((tab) => (
<button
type="button"
key={tab.id}
role="tab"
aria-selected={activeTab === tab.id}
onClick={() => setActiveTab(tab.id)}
style={{
padding: "6px 14px",
fontSize: "12px",
border: "none",
borderBottom:
activeTab === tab.id
? `2px solid ${tokens.colorBrandForeground1}`
: "2px solid transparent",
background: "transparent",
color:
activeTab === tab.id
? tokens.colorBrandForeground1
: tokens.colorNeutralForeground2,
fontWeight: activeTab === tab.id ? 600 : 400,
cursor: "pointer",
whiteSpace: "nowrap",
}}
>
{tab.label}
</button>
))}
</div>

{/* Tab content */}
<div role="tabpanel">{renderTabContent()}</div>
</div>

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The SettingsDialog tab UI uses ARIA roles (tablist/tab/tabpanel) but doesn’t implement the expected keyboard/focus behavior (roving tabIndex, ArrowLeft/ArrowRight/Home/End navigation) or link tabs to the panel via ids/aria-controls. This can make the dialog difficult to use with screen readers and keyboard-only navigation. Consider following the existing TabStrip tab pattern (tabIndex + arrow key handling) or using Fluent UI’s TabList/Tab components for correct accessibility semantics.

Copilot uses AI. Check for mistakes.
adamgell and others added 2 commits April 1, 2026 20:35
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r toggles

The previous approach let users toggle visibility of all possible columns
(sourceIp, macAddress, etc.) even when those columns don't exist in the
current log file. Columns are already correctly determined by the parser
based on file format. The Columns settings tab now only shows reset
options for custom column order and widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamgell
adamgell merged commit ae057c8 into main Apr 2, 2026
9 checks passed
adamgell added a commit that referenced this pull request Apr 5, 2026
Resolve 4 outstanding Copilot review issues plus changelog updates:

- DiffConfigDialog: filter to log-only tabs, validate selections exist
- FileSidebar: filter directories and uncached files before merge
- session.ts: replace unsafe cast with field-by-field validation and defaults
- session-restore: load files individually for per-tab restore, restore
  active tab index and scroll positions

Update changelog with all unreleased features, PR references (#72, #78,
#79, #81, #82), and new Security section for CI permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adamgell added a commit that referenced this pull request Apr 6, 2026
* feat(merge): add showMergeTabsDialog state to ui-store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add pure merge logic — sorting, colors, correlation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add MergedTabState, merge/correlation actions to log store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): create MergeLegendBar with file toggles and correlation controls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): create MergeTabsDialog component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add Merge Tabs button to toolbar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): wire MergeTabsDialog into AppShell

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): show merged tab indicator in tab strip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add file color borders, correlation highlights, and legend bar to log view

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add Merge into Timeline button to folder sidebar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add correlated entries section to InfoPane

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add unified timeline spec and implementation plan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add session save/restore and log diff specs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add recentSessions state to ui-store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add compute_file_hash Rust command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): add normalization, pattern key, and classification logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add session types, save, and restore logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): add DiffState and diff actions to log store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add Save/Open Session menu items and handlers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): add DiffConfigDialog, toolbar button, and ui-store state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): create DiffView with side-by-side and unified modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: wire DiffConfigDialog into AppShell, render DiffView, update changelog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address code review — scroll sync, session restore, validation, performance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Copilot PR review — accessibility, UTF-8, GUID casing, cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add Graph API GUID resolution (Windows)

Introduce end-to-end Microsoft Graph integration to resolve Intune app GUIDs. Backend: new src-tauri/src/graph_api.rs implements WAM-based Windows auth, token caching, batch/single Graph requests, paginated app fetches and an in-memory GUID cache; adds fetch_all_apps and resolve_guids utilities. Frontend: new GraphApiTab settings UI to enable/sign-in/pre-populate cache, a startup hook to auto-connect and populate cache, status bar indicator for Graph status, and enhancements to the GUID registry dialog (tabs, filtering, publisher/type columns). Add lib/graph-registry.ts to convert GraphAppInfo to GuidRegistry entries and update types (GuidCategory, publisher). Also improve event_tracker to extract PolicyId from JSON payloads. Wire startup import in main.tsx and add ui-store state/handlers for graphApiEnabled and graphApiStatus.

* feat: add Microsoft Graph API integration for GUID resolution

Add opt-in Graph API support to resolve Intune app GUIDs to display
names using the device's existing Entra ID session via WAM (Web Account
Manager). No app registration required — uses the Microsoft Graph
PowerShell public client ID.

- WAM authentication with HWND interop for Win32 desktop apps
- Graph API client with batch resolution ($batch endpoint, 20 per request)
- Pre-populate cache button fetches all tenant apps in one call
- GraphApi source variant (highest confidence) in GuidRegistry
- Settings tab with opt-in toggle (off by default), consent warnings,
  and connection status display
- Automatic enrichment during Intune log analysis when enabled

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts with Graph API integration, fix CI

- Fix analyze_intune_logs to use app.state() instead of cfg-gated parameter
  (Tauri generate_handler! can't handle #[cfg] on parameters)
- Gate Manager import to windows-only in both lib.rs and intune.rs
- Resolve merge conflicts in ui-store.ts (recentSessions + graphApi state)
- Resolve merge conflicts in GraphApiTab.tsx (keep buildGraphRegistryEntries)
- Fix GuidRegistryDialog aria-label to use entry.guid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR #82 review — ID collisions, persist sessions, correlation refresh, diff close

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: force clean build to resolve stale cache

* fix: add missing script_body and parent_app_guid to RFC3339 test

The sysmon merge brought a new test (build_timeline_sorts_rfc3339_timestamps)
that was missing the two fields added to IntuneEvent in the AppWorkload PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* security: add explicit permissions to CI workflow

Restrict GITHUB_TOKEN to contents:read across all jobs to satisfy
the principle of least privilege. Resolves CodeQL alerts #1#3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gate sysmon module behind feature flag, fix clippy

- Add sysmon feature to Cargo.toml, include in full feature set
- Gate pub mod sysmon and analyze_sysmon_logs command with #[cfg(feature = "sysmon")]
- Gate sysmon commands mod with #[cfg(feature = "sysmon")]
- Add required-features = ["sysmon"] to sysmon_parser test
- Fix clippy needless_range_loop in event_tracker.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts in graph_api.rs

Unresolved conflict markers from d065202 merge caused unclosed
delimiter errors on Windows CI builds. Kept the refactored
fetch_paginated helper that supports multiple Intune endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts from upstream session/diff features

Merge Graph API integration (graphApiEnabled, graphApiStatus) with
upstream session save/restore (recentSessions) and diff features.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: implement live Windows Event Log queries

Implement EvtOpenChannelEnum, EvtQuery, EvtNext, EvtRender, and
EvtFormatMessage via Win32 API to query live event log channels.
Auto-loads Application, System, Security, and Setup on "This Computer".

- Raw FFI for channel enumeration (fixes NULL handle issue)
- Buffer retry for EvtRender with both Win32 and HRESULT error codes
- Rendered messages via EvtFormatMessage with publisher metadata cache
- XML string parsing for EventID, Level, Provider, TimeCreated, EventData
- Progressive channel loading with per-channel error handling
- Channel picker: Event Viewer-style tree (Windows Logs / App & Services)
- Resizable channel sidebar with drag handle
- Resizable detail pane with drag handle
- Arrow key navigation in timeline (Up/Down/Home/End)
- Progress bar during channel loading
- Status bar shows "Event Log" with channel/event counts
- Load button for querying additional checked channels
- Error messages surfaced to frontend via errorMessages field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: live event log queries, Graph API scripts, parallel loading

Live Event Log:
- Implement Win32 EvtQuery/EvtRender/EvtFormatMessage for live channel queries
- Auto-load Application, System, Security, Setup in parallel
- Event Viewer-style nested tree sidebar (split on - and /)
- Resizable sidebar and detail pane with drag handles
- Arrow key navigation in timeline
- Progressive loading with spinner and elapsed time in status bar
- Refresh button to reload channels
- No event cap — loads all events on disk
- DevTools auto-open in debug builds

Graph API enhancements:
- Fetch remediation scripts, platform scripts, shell scripts
- GUID Registry tabbed view (All/Apps/Scripts/Remediations)
- Category and publisher columns
- Auto-connect on startup with status bar indicator
- PolicyId extraction for HealthScripts events

Fixes:
- HRESULT error code handling (low 16-bit extraction)
- EvtRender buffer retry for large events
- Status bar shows correct workspace labels
- Hide FileSidebar in Event Log workspace

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gate devtools and progress events for cross-platform CI

- Gate open_devtools behind #[cfg(all(debug_assertions, desktop))]
- Gate EvtxQueryProgress, Serialize, Emitter behind #[cfg(target_os = "windows")]
- Fixes cargo check on Ubuntu CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address remaining Copilot PR #82 review comments, update changelog

Resolve 4 outstanding Copilot review issues plus changelog updates:

- DiffConfigDialog: filter to log-only tabs, validate selections exist
- FileSidebar: filter directories and uncached files before merge
- session.ts: replace unsafe cast with field-by-field validation and defaults
- session-restore: load files individually for per-tab restore, restore
  active tab index and scroll positions

Update changelog with all unreleased features, PR references (#72, #78,
#79, #81, #82), and new Security section for CI permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add workspace registry design spec

Defines a pluggable workspace registry to replace 25+ scattered if/else
chains across 8 frontend files with centralized, type-safe definitions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add workspace registry implementation plan (Phase 1-2)

9 tasks covering foundation types, registry, and sysmon migration
as the template workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(workspaces): add WorkspaceDefinition types

* feat(workspaces): add central workspace registry

* refactor(sysmon): move sysmon types to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): move sysmon store to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): move sysmon components to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): move analysis progress hook to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): extract SysmonSidebar to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(workspaces): register sysmon as first workspace definition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add workspace registry Phase 3 implementation plan

6 tasks: shim definitions, AppShell/FileSidebar/ui-store/Toolbar refactors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(workspaces): add shim definitions for all workspaces

Creates shim WorkspaceDefinition objects for the 7 remaining workspaces
(log, intune, new-intune, dsregcmd, macos-diag, deployment, event-log),
exports LogSidebar/IntuneSidebar/DsregcmdSidebar from FileSidebar.tsx,
and registers all 8 workspaces in the workspace registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(appshell): use workspace registry for component routing

Replace the 8-way if/else chain in renderWorkspace() with a registry
lookup + Suspense for all non-log workspaces. Log workspace inline
rendering (DiffView, RegistryViewer, folder overlay, InfoPane) is
preserved as a special case. TabStrip and FindBar conditionals now
consult workspace capabilities flags instead of hard-coding activeView.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sidebar): use workspace registry for sidebar routing

Replace the ternary chain (isIntuneWorkspace / activeView === 'sysmon'
checks) with a registry lookup via getWorkspace(activeView).sidebar, and
drive the footer bar from capabilities.footerBar. Remove now-unused
SysmonSidebar direct import and isIntuneWorkspace import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(ui-store): delegate platform gating to workspace registry

Remove WORKSPACE_PLATFORM_MAP constant and inline filter logic from
getAvailableWorkspaces(). Delegate to getRegistryWorkspaces() from
the workspace registry instead, mapping WorkspaceDefinition[] back
to WorkspaceId[] to preserve the existing public API surface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(toolbar): use workspace registry for labels and file filters

Replace WORKSPACE_LABELS, getOpenFileDialogFilters(), and
getOpenActionLabels() with direct lookups from getWorkspace() in the
workspace registry, eliminating ~75 lines of duplicated metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(dsregcmd): migrate workspace to src/workspaces/dsregcmd/

Move all dsregcmd-specific files into src/workspaces/dsregcmd/ to make
the workspace fully self-contained, following the same pattern as sysmon.

- git mv types/dsregcmd.ts → workspaces/dsregcmd/types.ts
- git mv stores/dsregcmd-store.ts → workspaces/dsregcmd/dsregcmd-store.ts
- git mv components/dsregcmd/* → workspaces/dsregcmd/ (7 files)
- Extract DsregcmdSidebar from FileSidebar.tsx into workspaces/dsregcmd/DsregcmdSidebar.tsx
- Update index.ts shim to use local ./ imports
- Update all external importers (Toolbar, StatusBar, EvidenceBundleDialog,
  commands.ts, dsregcmd-source.ts, dsregcmd-store.test.ts, event-log/index.ts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(event-log): migrate workspace to src/workspaces/event-log/

Move types, store, and all 7 components out of src/types/, src/stores/,
and src/components/event-log-workspace/ into the unified workspace directory.
Update all internal imports to use relative paths within the workspace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(macos-diag): migrate workspace to src/workspaces/macos-diag/

Move types, store, and all 9 components from their scattered locations
(src/types/, src/stores/, src/components/macos-diag/) into the unified
workspace directory src/workspaces/macos-diag/. Update all internal
and external import paths accordingly. TypeScript passes clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(deployment): migrate workspace to src/workspaces/deployment/

Moves deployment-store.ts and all 3 component files (DeploymentWorkspace,
DeploymentErrorCard, DeploymentSuccessTable) from their legacy locations into
src/workspaces/deployment/. Updates all import paths in the moved files,
the workspace index shim, StatusBar.tsx, and both dynamic imports in Toolbar.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(intune): migrate workspace to src/workspaces/intune/

Moves all 19 intune components, the intune store (1,095 lines), types,
hook, and test file from their scattered locations into the consolidated
src/workspaces/intune/ directory. Extracts IntuneSidebar from
FileSidebar.tsx into its own file with helpers inlined. Updates all
importers across layout, dialogs, hooks, and lib modules. Both the
intune and new-intune workspace shims now reference local workspace paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove empty component directories after workspace migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add .superpowers/ to gitignore, remove tracked artifacts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract SourceSummaryCard to shared component, consolidate getBaseName

- Create src/components/common/sidebar-primitives.tsx with SourceSummaryCard,
  SourceStatusNotice, SectionHeader, EmptyState, and SidebarActionButton
- Remove inline copies of all five components from FileSidebar, IntuneSidebar,
  DsregcmdSidebar, and SysmonSidebar; import from shared module instead
- Widen canonical getBaseName signature to string | null | undefined
- Remove exported getBaseName from log-store; import from file-paths instead
- Update all callers in log-source, dsregcmd-source, EvidenceBundleDialog,
  StatusBar, IntuneSidebar, SysmonSidebar, and FileSidebar to use file-paths
- Also removes local getDirectoryName copy from EvidenceBundleDialog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(ui-store): replace getUiChromeStatus if/else chain with registry lookup

Add statusLabel to WorkspaceDefinition; set overrides on log, intune, and
new-intune workspaces. getUiChromeStatus now resolves labels via getWorkspace()
and branches only on capabilities.detailsPane, eliminating the 8-way if/else.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(toolbar): wire onOpenSource handlers and capability-based commandState

Move workspace-specific analysis logic (intune, dsregcmd, sysmon, deployment)
from Toolbar.tsx into each workspace's onOpenSource in its definition. Replace
the openSourceForWorkspace if/else chain with a registry lookup. Add knownSources
and tailing to WorkspaceCapabilities and use them in commandState instead of
hardcoded workspace ID checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update evtx-store import path after workspace migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Copilot PR #84 review comments

- event-log: remove incorrect DsregcmdSidebar, fix file filters to EVTX
- deployment: handle file sources by analyzing parent directory
- types: move IntuneTimestampBounds to shared types to break circular dep

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.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.

2 participants