Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
032dcf5
docs(qa): add canonical user-story tracker and dialog landmarks
adamgell Aug 18, 2026
9619578
fix(log): apply cached tab snapshot before folder restore
adamgell Aug 18, 2026
5d74650
fix(qa): align tracker and File Open IME/dsregcmd paths
adamgell Aug 18, 2026
58332b7
fix(qa): close CodeRabbit typecheck and tab-switch gaps
adamgell Aug 18, 2026
e06fc81
fix(timeline): keep empty folder opens as a no-op
adamgell Aug 18, 2026
67e3d3f
fix(qa): close CodeRabbit review gaps for tracker
adamgell Aug 18, 2026
edf71c0
test: isolate SecureBoot platform fixture
adamgell Aug 18, 2026
14da6cf
fix: close PR 577 review regressions
adamgell Aug 19, 2026
e8eaa1d
fix: close async source loading races
adamgell Aug 19, 2026
87074a4
fix: isolate dropped path failures and stale restores
adamgell Aug 19, 2026
b1a06a5
fix: harden IPC responses and stale source loads
adamgell Aug 19, 2026
8d4b4b9
test: stabilize remaining PR 577 fixtures
adamgell Aug 19, 2026
6180f42
fix: validate every frontend command response
adamgell Aug 19, 2026
28352ae
fix: align E2E association status fixture
adamgell Aug 19, 2026
dff078a
fix: keep workspace decoder IDs exhaustive
adamgell Aug 19, 2026
f2ef5b1
fix: derive IPC responses from decoder registry
adamgell Aug 19, 2026
0aaa618
fix: harden workspace source loading
adamgell Aug 19, 2026
306f8cc
fix: preserve stale source load semantics
adamgell Aug 19, 2026
932857e
fix: close review gaps in source loading and timeline actions
adamgell Aug 19, 2026
16fc8ad
fix: reset replacement and progress ownership synchronously
adamgell Aug 19, 2026
4262a77
fix: close remaining review findings
adamgell Aug 19, 2026
51f2aef
fix: propagate event log parse failures
adamgell Aug 19, 2026
c9e7331
fix: close remaining review findings
adamgell Aug 19, 2026
6e584ef
test: close PR 577 review gaps
adamgell Aug 19, 2026
6737f27
test: close remaining PR 577 review gaps
adamgell Aug 19, 2026
75b0ea3
fix: close remaining PR 577 review gaps
adamgell Aug 19, 2026
329fdcc
fix: close remaining PR 577 review gaps
adamgell Aug 19, 2026
b427fa9
test: close remaining PR 577 review gaps
adamgell Aug 19, 2026
de71d85
fix: close remaining PR 577 review gaps
adamgell Aug 19, 2026
65a3504
fix: cover keyboard and selection review gaps
adamgell Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.

### Added

- **Canonical user-story tracker**: Inventory every chrome and workspace feature in `docs/qa/user-stories.csv` with expected behavior derived from the current code, plus fixture/RTL coverage for those surfaces.

- **Administrator restart with source restoration (#384)**: Application-wide restart as administrator on supported Windows builds, restoring open sources after elevation.
- **Company Portal macOS unified-log evidence (#390)**: Normalize Apple unified-log evidence for Company Portal on macOS so enrollment and portal diagnostics correlate without inventing outcomes.
- **Intune Device Inventory Agent log family (#397 / #354)**: Discover and parse the full Microsoft Device Inventory Agent log set under Program Files (harvester, Inventory Adaptor, and rotation-failure dialects) with known-sources entry, folder aggregation for `.log` / rotations / `.log_`, and logical-record-aware real-time tailing.
Expand All @@ -29,6 +31,8 @@ All notable changes to this project will be documented in this file.

### Fixed

- **Dialog landmarks**: Filter, Collect Diagnostics, Collection Complete, Update, and first-run file-association overlays expose `role="dialog"` / `aria-modal` so they are reachable as dialog landmarks.

- **Unicode decimal digit panics (#413 / #502)**: Reject non-ASCII Unicode decimal fields in CCM and related time grammars so multi-byte digits cannot panic the parser mid-slice.
- **Signless CCM timestamp display (#410 / #504)**: Treat signless fractional tails as milliseconds (not fabricated timezone offsets); short fractions pad correctly for public `LogEntry` projection.
- **IPv6 redaction residual (#416 / #503)**: Redact bare unspecified IPv6 forms in macOS export paths without destroying C++ `std::` symbols.
Expand Down
119 changes: 119 additions & 0 deletions docs/qa/user-stories.csv

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/components/dialogs/AboutDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ describe("AboutDialog", () => {
getVersionMock.mockResolvedValue("1.3.2");
});

it("exposes a dialog landmark", async () => {
render(<AboutDialog isOpen onClose={() => {}} />);

const dialog = await screen.findByRole("dialog", { name: "About CMTrace Open" });
expect(dialog).toHaveAttribute("aria-modal", "true");
expect(await screen.findByText("CMTrace Open")).toBeVisible();
});

it("shows main channel app metadata", async () => {
render(<AboutDialog isOpen onClose={() => {}} />);

Expand Down
3 changes: 3 additions & 0 deletions src/components/dialogs/AboutDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export function AboutDialog({ isOpen, onClose }: AboutDialogProps) {
}}
>
<div
role="dialog"
aria-modal="true"
aria-label="About CMTrace Open"
Comment thread
adamgell marked this conversation as resolved.
style={{
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
Expand Down
101 changes: 101 additions & 0 deletions src/components/dialogs/CollectDiagnosticsDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { CollectDiagnosticsDialog } from "./CollectDiagnosticsDialog";
import { CollectionCompleteDialog } from "./CollectionCompleteDialog";
import { useUiStore } from "../../stores/ui-store";
import { COLLECTION_PRESETS } from "../../lib/collection-categories";

const collectDiagnostics = vi.hoisted(() => vi.fn());

vi.mock("../../lib/commands", () => ({
collectDiagnostics,
}));

vi.mock("../../lib/log-source", () => ({
loadPathAsLogSource: vi.fn(),
}));

describe("CollectDiagnosticsDialog", () => {
afterEach(() => {
cleanup();
useUiStore.setState({ collectionProgress: null, collectionResult: null });
});

it("exposes a dialog landmark when open", () => {
render(<CollectDiagnosticsDialog isOpen onClose={() => {}} />);
const dialog = screen.getByRole("dialog", { name: "Collect Diagnostics" });
expect(dialog).toHaveAttribute("aria-modal", "true");
});

it("shows presets, category checkboxes, and starts collection", async () => {
collectDiagnostics.mockResolvedValue({
bundlePath: "C:/Users/Public/cmtrace-bundle",
bundleId: "bundle-1",
artifactCounts: { collected: 4, missing: 1, failed: 0, total: 5 },
durationMs: 1200,
gaps: [{ artifactId: "cbs", category: "general", reason: "not present" }],
});
const onClose = vi.fn();
render(<CollectDiagnosticsDialog isOpen onClose={onClose} />);

expect(screen.getByText("Collect Diagnostics")).toBeInTheDocument();
expect(screen.getByText("Quick Presets")).toBeInTheDocument();
for (const preset of COLLECTION_PRESETS) {
expect(screen.getByRole("button", { name: preset.label })).toBeInTheDocument();
}
expect(screen.getByText("Intune & MDM")).toBeInTheDocument();
expect(screen.getByText("Autopilot & Provisioning")).toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Intune + Autopilot" }));
fireEvent.click(screen.getByRole("button", { name: "Collect" }));
expect(onClose).toHaveBeenCalled();
expect(collectDiagnostics).toHaveBeenCalled();
});
});

describe("CollectionCompleteDialog", () => {
afterEach(() => {
cleanup();
});

it("exposes a dialog landmark when complete", () => {
render(
<CollectionCompleteDialog
onClose={() => {}}
result={{
bundlePath: "C:/Users/Public/cmtrace-bundle",
bundleId: "bundle-1",
artifactCounts: { collected: 4, missing: 1, failed: 0, total: 5 },
durationMs: 1500,
gaps: [],
}}
/>,
);
const dialog = screen.getByRole("dialog", { name: "Collection Complete" });
expect(dialog).toHaveAttribute("aria-modal", "true");
});

it("shows counts, gaps, Close, and Open Bundle", () => {
const onClose = vi.fn();
render(
<CollectionCompleteDialog
onClose={onClose}
result={{
bundlePath: "C:/Users/Public/cmtrace-bundle",
bundleId: "bundle-1",
artifactCounts: { collected: 4, missing: 1, failed: 0, total: 5 },
durationMs: 1500,
gaps: [{ artifactId: "cbs", category: "general", reason: "CBS.log not present" }],
}}
/>,
);
expect(screen.getByText("Collection Complete")).toBeInTheDocument();
expect(screen.getByText("Collected")).toBeInTheDocument();
expect(screen.getByText("Missing")).toBeInTheDocument();
expect(screen.getByText("Failed")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /Show 1 missing/i }));
expect(screen.getByText(/CBS.log not present/)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Close" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Open Bundle" })).toBeInTheDocument();
});
});
3 changes: 3 additions & 0 deletions src/components/dialogs/CollectDiagnosticsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ export function CollectDiagnosticsDialog({ isOpen, onClose }: CollectDiagnostics
}}
>
<div
role="dialog"
aria-modal="true"
aria-label="Collect Diagnostics"
Comment thread
adamgell marked this conversation as resolved.
style={{
width: "520px",
maxHeight: "80vh",
Expand Down
3 changes: 3 additions & 0 deletions src/components/dialogs/CollectionCompleteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export function CollectionCompleteDialog({ result, onClose }: CollectionComplete
}}
>
<div
role="dialog"
aria-modal="true"
aria-label={isError ? "Collection Failed" : "Collection Complete"}
style={{
width: "420px",
backgroundColor: tokens.colorNeutralBackground1,
Expand Down
175 changes: 175 additions & 0 deletions src/components/dialogs/EvidenceBundleDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { inspectEvidenceArtifact, inspectEvidenceBundle } from "../../lib/commands";
import { useLogStore } from "../../stores/log-store";
import { useUiStore } from "../../stores/ui-store";
import type { EvidenceBundleDetails, EvidenceBundleMetadata } from "../../types/evidence";
import { EvidenceBundleDialog } from "./EvidenceBundleDialog";

vi.mock("../../lib/commands", () => ({
inspectEvidenceBundle: vi.fn(),
inspectEvidenceArtifact: vi.fn(),
}));

vi.mock("../../hooks/use-app-actions", () => ({
useAppActions: () => ({
openPathForActiveWorkspace: vi.fn(),
}),
}));

const inspectEvidenceBundleMock = vi.mocked(inspectEvidenceBundle);
const inspectEvidenceArtifactMock = vi.mocked(inspectEvidenceArtifact);

function metadata(overrides: Partial<EvidenceBundleMetadata> = {}): EvidenceBundleMetadata {
return {
manifestPath: "/tmp/bundle/manifest.json",
notesPath: "/tmp/bundle/notes.md",
evidenceRoot: "/tmp/bundle/evidence",
primaryEntryPoints: ["evidence/ime.log"],
availablePrimaryEntryPoints: ["evidence/ime.log"],
bundleId: "bundle-fixture",
bundleLabel: "Fixture evidence bundle",
createdUtc: "2026-08-18T12:00:00Z",
caseReference: "CASE-018",
summary: "Minimal fixture inventory.",
collectorProfile: "quick",
collectorVersion: "1.0.0",
collectedUtc: "2026-08-18T12:00:00Z",
deviceName: "TEST-PC",
primaryUser: "analyst",
platform: "windows",
osVersion: "10.0.26100",
tenant: "contoso",
artifactCounts: {
collected: 0,
missing: 0,
failed: 0,
skipped: 0,
},
...overrides,
};
}

function details(overrides: Partial<EvidenceBundleDetails> = {}): EvidenceBundleDetails {
const meta = overrides.metadata ?? metadata();
return {
bundleRootPath: "/tmp/bundle",
metadata: meta,
manifestContent: '{"bundleId":"bundle-fixture"}',
notesContent: "Collector notes.",
artifacts: [],
expectedEvidence: [],
observedGaps: [],
priorityQuestions: [],
handoffSummary: null,
...overrides,
};
}

function tabButton(container: HTMLElement, name: string): HTMLButtonElement {
const button = Array.from(container.querySelectorAll("button")).find(
(element) => element.textContent === name,
);
if (!(button instanceof HTMLButtonElement)) {
throw new Error(`Missing tab button: ${name}`);
}
return button;
}

describe("EvidenceBundleDialog", () => {
beforeEach(() => {
vi.clearAllMocks();
useUiStore.setState({
activeView: "log",
activeWorkspace: "log",
});
useLogStore.getState().clear();
inspectEvidenceBundleMock.mockResolvedValue(details());
inspectEvidenceArtifactMock.mockResolvedValue({
path: "/tmp/bundle/evidence/ime.log",
intakeKind: "log",
summary: "log preview",
registrySnapshot: null,
eventLogExport: null,
});
});

it("renders nothing without bundle metadata", () => {
render(<EvidenceBundleDialog isOpen onClose={() => {}} />);
expect(screen.queryByText("Evidence Bundle")).not.toBeInTheDocument();
expect(inspectEvidenceBundleMock).not.toHaveBeenCalled();
});

it("opens Summary/Inventory/Notes/Manifest and empty inventory copy from a store fixture", async () => {
useLogStore.getState().setBundleMetadata(metadata());
useLogStore.getState().setActiveSource({
kind: "folder",
path: "/tmp/bundle",
});

const { container } = render(<EvidenceBundleDialog isOpen onClose={() => {}} />);

const dialog = container.querySelector('[role="dialog"]');
expect(dialog).not.toBeNull();
expect(dialog).toHaveAttribute("aria-modal", "true");
expect(dialog).toHaveAttribute("aria-label", "Evidence bundle summary");
expect(screen.getByText("Fixture evidence bundle")).toBeInTheDocument();
expect(screen.getByText("Minimal fixture inventory.")).toBeInTheDocument();
expect(tabButton(container, "Summary")).toHaveAttribute("aria-pressed", "true");
expect(tabButton(container, "Inventory")).toBeTruthy();
expect(tabButton(container, "Notes")).toBeTruthy();
expect(tabButton(container, "Manifest")).toBeTruthy();
expect(screen.getByText("Bundle metadata")).toBeInTheDocument();
expect(screen.getByText("Primary evidence entry points")).toBeInTheDocument();
expect(tabButton(container, "Close")).toBeTruthy();

await waitFor(() => {
expect(inspectEvidenceBundleMock).toHaveBeenCalledWith("/tmp/bundle");
});
await waitFor(() => {
expect(screen.queryByText("Loading evidence bundle details...")).not.toBeInTheDocument();
});

fireEvent.click(tabButton(container, "Inventory"));
expect(tabButton(container, "Inventory")).toHaveAttribute("aria-pressed", "true");
expect(
screen.getByText("No artifact records were found in the manifest."),
).toBeInTheDocument();
expect(
screen.getByText("No intake diagnostics are available yet."),
).toBeInTheDocument();
expect(screen.getByText("No artifact detail was available.")).toBeInTheDocument();
expect(
screen.getByText("No expected-evidence detail was recorded in the manifest."),
).toBeInTheDocument();

fireEvent.click(tabButton(container, "Notes"));
expect(tabButton(container, "Notes")).toHaveAttribute("aria-pressed", "true");
expect(screen.getByText("Collector notes.")).toBeInTheDocument();

fireEvent.click(tabButton(container, "Manifest"));
expect(tabButton(container, "Manifest")).toHaveAttribute("aria-pressed", "true");
expect(screen.getByText('{"bundleId":"bundle-fixture"}')).toBeInTheDocument();
});

it("shows inspect error copy while keeping the dialog and tabs visible", async () => {
useLogStore.getState().setBundleMetadata(metadata());
useLogStore.getState().setActiveSource({
kind: "folder",
path: "/tmp/bundle",
});
inspectEvidenceBundleMock.mockRejectedValue(new Error("inspect failed"));

const { container } = render(<EvidenceBundleDialog isOpen onClose={() => {}} />);

expect(await screen.findByText("inspect failed")).toBeInTheDocument();
expect(container.querySelector('[role="dialog"]')).not.toBeNull();
expect(tabButton(container, "Summary")).toBeTruthy();
expect(tabButton(container, "Inventory")).toBeTruthy();
expect(tabButton(container, "Notes")).toBeTruthy();
expect(tabButton(container, "Manifest")).toBeTruthy();

fireEvent.click(tabButton(container, "Notes"));
expect(screen.getByText("No content was available for this file.")).toBeInTheDocument();
});
});
3 changes: 3 additions & 0 deletions src/components/dialogs/FileAssociationPromptDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export function FileAssociationPromptDialog({
}}
>
<div
role="dialog"
aria-modal="true"
aria-label="Associate log files with CMTrace Open?"
style={{
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
Expand Down
19 changes: 19 additions & 0 deletions src/components/dialogs/FilterDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { FilterDialog } from "./FilterDialog";

describe("FilterDialog", () => {
it("exposes a dialog landmark when open", () => {
render(
<FilterDialog
isOpen
onClose={() => {}}
onApply={async () => undefined}
currentClauses={[]}
/>,
);

const dialog = screen.getByRole("dialog", { name: "Filter" });
expect(dialog).toHaveAttribute("aria-modal", "true");
});
});
3 changes: 3 additions & 0 deletions src/components/dialogs/FilterDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ export function FilterDialog({
}}
>
<div
role="dialog"
aria-modal="true"
aria-label="Filter"
style={{
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
Expand Down
Loading
Loading