Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions packages/summit-cogbattlespace/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ export interface CogBattleStorage {
listTopNarratives(limit: number): Promise<Narrative[]>;
listBeliefs(limit: number): Promise<Belief[]>;
listDivergence(narrativeId?: string): Promise<DivergenceMetric[]>;
getCurrentEntity(entityType: string, fingerprint: string): Promise<any>;
putLaneSnapshot(entityType: string, fingerprint: string, data: any): Promise<void>;
Comment on lines +30 to +31
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The use of any for getCurrentEntity's return type and putLaneSnapshot's data parameter circumvents TypeScript's type safety. This can lead to runtime errors and makes the code harder to maintain and understand. Consider using a generic type parameter for the entity data, for example <T>, to provide type safety for consumers of this storage interface.

For example:

export interface CogBattleStorage {
  // ... other methods
  getCurrentEntity<T>(entityType: string, fingerprint: string): Promise<T | null>;
  putLaneSnapshot<T>(entityType: string, fingerprint: string, data: T): Promise<void>;
}

This would require updating InMemoryCogBattleStorage as well, but would significantly improve type safety.

Comment on lines +30 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify call sites and check whether callers currently depend on untyped `any` behavior.
rg -nP --type=ts -C2 '\b(getCurrentEntity|putLaneSnapshot)\s*\('

Repository: BrianCLong/summit

Length of output: 2420


🏁 Script executed:

#!/bin/bash
# Find the reconcileEntityTrustAware function and see what type it expects for 'current'
rg -nP --type=ts 'reconcileEntityTrustAware' -A5 -B2

Repository: BrianCLong/summit

Length of output: 2463


🏁 Script executed:

#!/bin/bash
# Check if there are any other imports or implementations of CogBattleStorage interface
rg -nP --type=ts 'implements CogBattleStorage|CogBattleStorage' -B1 -A3

Repository: BrianCLong/summit

Length of output: 7681


🏁 Script executed:

#!/bin/bash
# Look at the context around the firewall.ts call sites to understand type expectations
sed -n '190,240p' packages/summit-cogbattlespace/src/writeset/firewall.ts

Repository: BrianCLong/summit

Length of output: 2514


🏁 Script executed:

#!/bin/bash
# Find EntitySnapshot type definition
rg -nP --type=ts 'type EntitySnapshot|interface EntitySnapshot' -B2 -A5

Repository: BrianCLong/summit

Length of output: 759


🏁 Script executed:

#!/bin/bash
# Check what is imported in reconciler.ts to understand type context
head -30 packages/summit-cogbattlespace/src/writeset/reconcile/reconciler.ts

Repository: BrianCLong/summit

Length of output: 1272


Replace any with concrete EntitySnapshot type for storage API contract.

The interface returns and accepts any, which bypasses strict type checking. getCurrentEntity() should return Promise<EntitySnapshot | null> (matching what reconcileEntityTrustAware() expects), and putLaneSnapshot() should accept EntitySnapshot. The current workaround as any cast on line 201 of firewall.ts is a symptom of this weak typing.

Recommended fix
+ import type { EntitySnapshot } from "./writeset/reconcile/types";

 export interface CogBattleStorage {
-  getCurrentEntity(entityType: string, fingerprint: string): Promise<any>;
-  putLaneSnapshot(entityType: string, fingerprint: string, data: any): Promise<void>;
+  getCurrentEntity(entityType: string, fingerprint: string): Promise<EntitySnapshot | null>;
+  putLaneSnapshot(entityType: string, fingerprint: string, data: EntitySnapshot): Promise<void>;
 }

 export class InMemoryCogBattleStorage implements CogBattleStorage {
-  private laneSnapshots = new Map<string, Map<string, any>>();
+  private laneSnapshots = new Map<string, Map<string, EntitySnapshot>>();

-  async getCurrentEntity(entityType: string, fingerprint: string): Promise<any> {
+  async getCurrentEntity(entityType: string, fingerprint: string): Promise<EntitySnapshot | null> {
     const typeMap = this.laneSnapshots.get(entityType);
     if (!typeMap) return null;
     return typeMap.get(fingerprint) ?? null;
   }

-  async putLaneSnapshot(entityType: string, fingerprint: string, data: any): Promise<void> {
+  async putLaneSnapshot(entityType: string, fingerprint: string, data: EntitySnapshot): Promise<void> {
     let typeMap = this.laneSnapshots.get(entityType);
     if (!typeMap) {
-      typeMap = new Map();
+      typeMap = new Map<string, EntitySnapshot>();
       this.laneSnapshots.set(entityType, typeMap);
     }
     typeMap.set(fingerprint, data);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/summit-cogbattlespace/src/storage.ts` around lines 30 - 31, Update
the storage interface so it uses the concrete EntitySnapshot type instead of
any: change getCurrentEntity to return Promise<EntitySnapshot | null> and change
putLaneSnapshot to accept data: EntitySnapshot; then update call sites (notably
reconcileEntityTrustAware and the place casting with "as any" in firewall.ts) to
stop using casts and work with the stronger types, adjusting imports to
reference the EntitySnapshot type where needed.

}

export class InMemoryCogBattleStorage implements CogBattleStorage {
private laneSnapshots = new Map<string, Map<string, any>>();
private readonly artifacts = new Map<string, Artifact>();
private readonly narratives = new Map<string, Narrative>();
private readonly beliefs = new Map<string, Belief>();
Expand Down Expand Up @@ -88,4 +91,19 @@ export class InMemoryCogBattleStorage implements CogBattleStorage {
}
return this.divergence.filter((item) => item.narrativeId === narrativeId);
}

async getCurrentEntity(entityType: string, fingerprint: string): Promise<any> {
const typeMap = this.laneSnapshots.get(entityType);
if (!typeMap) return null;
return typeMap.get(fingerprint) ?? null;
}

async putLaneSnapshot(entityType: string, fingerprint: string, data: any): Promise<void> {
let typeMap = this.laneSnapshots.get(entityType);
if (!typeMap) {
typeMap = new Map();
this.laneSnapshots.set(entityType, typeMap);
}
typeMap.set(fingerprint, data);
}
}
3 changes: 2 additions & 1 deletion packages/summit-cogbattlespace/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.test.ts"]
}
35 changes: 35 additions & 0 deletions packages/summit-ui/src/components/cogbattlespace/ExplainDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

export function ExplainDrawer(props: {
open: boolean;
onClose: () => void;
title: string;
body: string;
disclaimers: string[];
}) {
Comment on lines +3 to +9
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For better readability and reusability, it's a good practice to define component props in a separate interface or type alias.

Suggested change
export function ExplainDrawer(props: {
open: boolean;
onClose: () => void;
title: string;
body: string;
disclaimers: string[];
}) {
type ExplainDrawerProps = {
open: boolean;
onClose: () => void;
title: string;
body: string;
disclaimers: string[];
};
export function ExplainDrawer(props: ExplainDrawerProps) {

if (!props.open) return null;

return (
<div className="fixed inset-0 bg-black/40 flex justify-end">
<div className="w-full max-w-xl h-full bg-white p-6 overflow-auto">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold">{props.title}</h2>
<button className="px-3 py-1 rounded-2xl border" onClick={props.onClose}>
Close
</button>
</div>

<pre className="mt-4 whitespace-pre-wrap text-sm">{props.body}</pre>

<div className="mt-6">
<h3 className="text-sm font-semibold">Defensive disclaimers</h3>
<ul className="mt-2 list-disc pl-5 text-sm">
{props.disclaimers.map((d, i) => (
<li key={i}>{d}</li>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using an array index as a key is an anti-pattern in React when the list of items can be reordered, added to, or removed from. This can lead to incorrect component state and rendering issues. If the disclaimer strings are not guaranteed to be unique, a more robust key can be generated by combining the string and its index.

Suggested change
<li key={i}>{d}</li>
<li key={`${d}-${i}`}>{d}</li>

))}
</ul>
</div>
</div>
</div>
);
}
30 changes: 30 additions & 0 deletions packages/summit-ui/src/components/cogbattlespace/LayerToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";

export type Layer = "reality" | "narrative" | "belief";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To ensure the Layer type and the array of layers used for rendering are always in sync, it's better to define the array as a const and derive the type from it. This avoids potential mismatches if the Layer type is updated in the future but the hardcoded array in the component is not.

Suggested change
export type Layer = "reality" | "narrative" | "belief";
export const LAYERS = ["reality", "narrative", "belief"] as const;
export type Layer = typeof LAYERS[number];


export function LayerToggle(props: {
enabled: Record<Layer, boolean>;
onChange: (next: Record<Layer, boolean>) => void;
}) {
Comment on lines +5 to +8
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For better readability and reusability, it's a good practice to define component props in a separate interface or type alias.

Suggested change
export function LayerToggle(props: {
enabled: Record<Layer, boolean>;
onChange: (next: Record<Layer, boolean>) => void;
}) {
type LayerToggleProps = {
enabled: Record<Layer, boolean>;
onChange: (next: Record<Layer, boolean>) => void;
};
export function LayerToggle(props: LayerToggleProps) {

const { enabled, onChange } = props;

const toggle = (k: Layer) => {
onChange({ ...enabled, [k]: !enabled[k] });
};

return (
<div className="flex gap-2 items-center">
{(["reality", "narrative", "belief"] as Layer[]).map((k) => (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Following the suggestion to create a LAYERS constant, you should iterate over it here instead of a hardcoded array.

Suggested change
{(["reality", "narrative", "belief"] as Layer[]).map((k) => (
{LAYERS.map((k) => (

<button
key={k}
className={`px-3 py-1 rounded-2xl border text-sm ${
enabled[k] ? "bg-black text-white" : "bg-white"
}`}
onClick={() => toggle(k)}
>
{k}
</button>
))}
</div>
);
}
46 changes: 46 additions & 0 deletions packages/summit-ui/src/components/cogbattlespace/MetricsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";

export function MetricsPanel(props: {
narratives: Array<{ id: string; label: string; summary: string; metrics: { velocity: number } }>;
divergence: Array<{ narrativeId: string; claimId: string; divergenceScore: number }>;
onExplain: (narrativeId: string) => void;
}) {
Comment on lines +3 to +7
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For better readability and reusability, it's a good practice to define component props in a separate interface or type alias. The complex inline types for narratives and divergence would also benefit from being extracted into their own named types.

type Narrative = {
  id: string;
  label: string;
  summary: string;
  metrics: { velocity: number };
};

type Divergence = {
  narrativeId: string;
  claimId: string;
  divergenceScore: number;
};

interface MetricsPanelProps {
  narratives: Narrative[];
  divergence: Divergence[];
  onExplain: (narrativeId: string) => void;
}

export function MetricsPanel(props: MetricsPanelProps) {

return (
<div className="grid gap-4">
<div className="rounded-2xl border p-4">
<h3 className="text-sm font-semibold">Top narratives (velocity)</h3>
<div className="mt-3 grid gap-2">
{props.narratives.map((n) => (
<div key={n.id} className="p-3 rounded-xl border">
<div className="flex items-center justify-between">
<div className="font-medium">{n.label}</div>
<div className="text-xs opacity-70">v={n.metrics.velocity.toFixed(2)}</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against undefined values before calling .toFixed().

If n.metrics.velocity is undefined or null, calling .toFixed(2) will throw a runtime error. Same applies to d.divergenceScore on line 39.

🛡️ Proposed defensive fix
-                <div className="text-xs opacity-70">v={n.metrics.velocity.toFixed(2)}</div>
+                <div className="text-xs opacity-70">v={(n.metrics.velocity ?? 0).toFixed(2)}</div>

And for line 39:

-              <div className="text-xs opacity-70">score={d.divergenceScore.toFixed(2)}</div>
+              <div className="text-xs opacity-70">score={(d.divergenceScore ?? 0).toFixed(2)}</div>
📝 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.

Suggested change
<div className="text-xs opacity-70">v={n.metrics.velocity.toFixed(2)}</div>
<div className="text-xs opacity-70">v={(n.metrics.velocity ?? 0).toFixed(2)}</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/summit-ui/src/components/cogbattlespace/MetricsPanel.tsx` at line
17, Guard against undefined/null before calling .toFixed() on numeric fields:
update the render in MetricsPanel.tsx where n.metrics.velocity and
d.divergenceScore are used (search for the symbols n.metrics.velocity and
d.divergenceScore) to first check for a valid number (e.g., Number.isFinite(...)
or nullish-coalesce to a default like 0) and only call .toFixed(2) when the
value is numeric; otherwise render a safe fallback (such as "-" or "0.00") so no
runtime error occurs.

</div>
<div className="text-sm opacity-80 mt-1">{n.summary}</div>
<button
className="mt-2 px-3 py-1 rounded-2xl border text-sm"
onClick={() => props.onExplain(n.id)}
>
Explain
</button>
</div>
))}
</div>
</div>

<div className="rounded-2xl border p-4">
<h3 className="text-sm font-semibold">Divergence signals</h3>
<div className="mt-3 grid gap-2">
{props.divergence.map((d, idx) => (
<div key={`${d.narrativeId}-${idx}`} className="p-3 rounded-xl border">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using an array index as part of the key is an anti-pattern if the list can be reordered. Since each divergence item has a narrativeId and a claimId, a composite key from these two properties would be more stable and is preferred over using the index.

Suggested change
<div key={`${d.narrativeId}-${idx}`} className="p-3 rounded-xl border">
<div key={`${d.narrativeId}-${d.claimId}`} className="p-3 rounded-xl border">

<div className="text-sm">
narrative={d.narrativeId} → claim={d.claimId}
</div>
<div className="text-xs opacity-70">score={d.divergenceScore.toFixed(2)}</div>
</div>
))}
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from "react";

type RejectionError = {
code: string;
message: string;
instancePath?: string;
schemaPath?: string;
};

type RejectionItem = {
opId: string;
status: "ACCEPTED" | "REJECTED";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Align rejection statuses with writeset outcomes

The panel hardcodes item status to "ACCEPTED" | "REJECTED", but the only writeset producer in this repo (applyCogWriteSet in packages/summit-cogbattlespace/src/writeset/firewall.ts) emits statuses like APPLIED, MERGED, PROMOTED, NOOP_ENTITY, etc. When this component is fed real API data, successful operations will not map correctly and will be rendered with the non-accepted styling branch, misreporting valid writes as failures.

Useful? React with 👍 / 👎.

entityType?: string;
domain?: string;
action?: string;
errors?: RejectionError[];
};

type RejectionReport = {
ok: boolean;
writesetId: string;
summary: { receivedOps: number; acceptedOps: number; rejectedOps: number };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align summary counters with writeset report fields

The summary shape expects acceptedOps, but the backend writeset report model provides granular counters (appliedOps, mergedOps, promotedOps, etc.) and no acceptedOps field. If this component is connected to the existing API contract, the accepted count will render as undefined, which breaks the report header and obscures operator outcomes.

Useful? React with 👍 / 👎.

items: RejectionItem[];
};

export function RejectionReportPanel({ report }: { report: RejectionReport }) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For consistency and clarity, it's better to define component props in a named interface.

interface RejectionReportPanelProps {
  report: RejectionReport;
}

export function RejectionReportPanel({ report }: RejectionReportPanelProps) {

return (
<div className="rounded-2xl border p-4">
<div className="flex items-center justify-between">
<div className="text-sm font-semibold">Write Report</div>
<div className={`text-xs px-2 py-1 rounded-xl border ${report.ok ? "bg-white" : "bg-black text-white"}`}>
{report.ok ? "OK" : "REJECTED"}
</div>
</div>

<div className="mt-2 text-xs opacity-70">
writeset={report.writesetId} · received={report.summary.receivedOps} · accepted={report.summary.acceptedOps} ·
rejected={report.summary.rejectedOps}
</div>

<div className="mt-4 grid gap-2">
{report.items.map((it) => (
<div key={it.opId} className="p-3 rounded-xl border">
<div className="flex items-center justify-between">
<div className="text-sm font-medium">
{it.opId}{" "}
<span className="text-xs opacity-70">
{it.domain ? `· ${it.domain}` : ""} {it.entityType ? `· ${it.entityType}` : ""}{" "}
{it.action ? `· ${it.action}` : ""}
</span>
</div>
<div className={`text-xs px-2 py-1 rounded-xl border ${it.status === "ACCEPTED" ? "" : "bg-black text-white"}`}>
{it.status}
</div>
</div>

{it.errors?.length ? (
<ul className="mt-2 list-disc pl-5 text-xs">
{it.errors.map((e, idx) => (
<li key={idx}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using an array index as a key is an anti-pattern in React when the list of items can change. This can lead to incorrect component state and rendering issues. A more stable key can be created by combining the index with other properties of the error object, like code and instancePath.

Suggested change
<li key={idx}>
<li key={`${idx}-${e.code}-${e.instancePath ?? ''}`}>

<span className="font-semibold">{e.code}</span>: {e.message}
{e.instancePath ? <span className="opacity-70"> · path={e.instancePath}</span> : null}
{e.schemaPath ? <span className="opacity-70"> · schema={e.schemaPath}</span> : null}
</li>
))}
</ul>
) : (
<div className="mt-2 text-xs opacity-70">No errors.</div>
)}
</div>
))}
</div>
</div>
);
}
93 changes: 93 additions & 0 deletions packages/summit-ui/src/pages/cogbattlespace/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useMemo, useState } from "react";
import { LayerToggle, Layer } from "../../components/cogbattlespace/LayerToggle";
import { MetricsPanel } from "../../components/cogbattlespace/MetricsPanel";
import { ExplainDrawer } from "../../components/cogbattlespace/ExplainDrawer";

// Replace these with your real API client
async function fetchTopNarratives() {
return [];
}
async function fetchDivergence() {
return [];
}

export default function CognitiveBattlespacePage() {
const [layers, setLayers] = useState<Record<Layer, boolean>>({
reality: true,
narrative: true,
belief: true
});

const [narratives, setNarratives] = useState<any[]>([]);
const [divergence, setDivergence] = useState<any[]>([]);
Comment on lines +6 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Using any[] for state and not providing return types for fetch functions undermines TypeScript's type safety. It's best to define strong types for your data structures (Narrative, Divergence) and use them consistently in your state and API functions. This will prevent runtime errors and improve code maintainability. These types could be shared with the MetricsPanel component, for example by moving them to a shared types file.

// It's best to define these types in a shared file and import them.
type Narrative = {
  id: string;
  label: string;
  summary: string;
  metrics: { velocity: number };
};

type Divergence = {
  narrativeId: string;
  claimId: string;
  divergenceScore: number;
};

// Replace these with your real API client
async function fetchTopNarratives(): Promise<Narrative[]> {
  return [];
}
async function fetchDivergence(): Promise<Divergence[]> {
  return [];
}

export default function CognitiveBattlespacePage() {
  const [layers, setLayers] = useState<Record<Layer, boolean>>({
    reality: true,
    narrative: true,
    belief: true,
  });

  const [narratives, setNarratives] = useState<Narrative[]>([]);
  const [divergence, setDivergence] = useState<Divergence[]>([]);

Comment on lines +21 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid any[] - define proper types for state.

Using any[] defeats TypeScript's purpose. Define types matching your API response or reuse the types from MetricsPanel props.

♻️ Proposed type definitions
+type Narrative = {
+  id: string;
+  label: string;
+  summary: string;
+  metrics: { velocity: number };
+};
+
+type DivergenceSignal = {
+  narrativeId: string;
+  claimId: string;
+  divergenceScore: number;
+};
+
 export default function CognitiveBattlespacePage() {
   // ...
-  const [narratives, setNarratives] = useState<any[]>([]);
-  const [divergence, setDivergence] = useState<any[]>([]);
+  const [narratives, setNarratives] = useState<Narrative[]>([]);
+  const [divergence, setDivergence] = useState<DivergenceSignal[]>([]);
📝 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.

Suggested change
const [narratives, setNarratives] = useState<any[]>([]);
const [divergence, setDivergence] = useState<any[]>([]);
type Narrative = {
id: string;
label: string;
summary: string;
metrics: { velocity: number };
};
type DivergenceSignal = {
narrativeId: string;
claimId: string;
divergenceScore: number;
};
export default function CognitiveBattlespacePage() {
const [narratives, setNarratives] = useState<Narrative[]>([]);
const [divergence, setDivergence] = useState<DivergenceSignal[]>([]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/summit-ui/src/pages/cogbattlespace/index.tsx` around lines 21 - 22,
Replace the loose any[] state with concrete types: define interfaces (e.g.,
Narrative and Divergence) that match the API/props shape or import/reuse the
existing types used by MetricsPanel, then update the useState generics for
narratives and divergence to use those types (useState<Narrative[]>() /
useState<Divergence[]>()), adjust setNarratives/setDivergence usages to satisfy
the new types, and add any necessary imports for the types at the top of the
file (or export the types from MetricsPanel and consume them here).


const [drawerOpen, setDrawerOpen] = useState(false);
const [drawerTitle, setDrawerTitle] = useState("Explain");
const [drawerBody, setDrawerBody] = useState("");
const [drawerDisclaimers, setDrawerDisclaimers] = useState<string[]>([]);

React.useEffect(() => {
(async () => {
setNarratives(await fetchTopNarratives());
setDivergence(await fetchDivergence());
})();
}, []);

const enabledLayers = useMemo(
() => Object.entries(layers).filter(([, v]) => v).map(([k]) => k),
[layers]
);

const explain = async (narrativeId: string) => {
// Stubbed explain content; wire to summit-cogbattlespace explainDivergence endpoint later
setDrawerTitle(`Explain: ${narrativeId}`);
setDrawerBody(
[
"This view is analytic/defensive.",
"It explains why a narrative was flagged and what evidence-backed claims it may conflict with.",
"",
"No counter-messaging guidance is generated."
].join("\n")
);
setDrawerDisclaimers([
"Analytic/defensive only: no persuasion or targeting guidance.",
"Heuristic scores; review artifacts + evidence.",
"Association is not causation."
]);
setDrawerOpen(true);
};

return (
<div className="p-6 grid gap-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold">Cognitive Battlespace</h1>
<div className="text-sm opacity-70 mt-1">
Layers enabled: {enabledLayers.join(", ")}
</div>
</div>
<LayerToggle enabled={layers} onChange={setLayers} />
</div>

{/* Placeholder for map/graph canvas */}
<div className="rounded-2xl border p-6 min-h-[260px]">
<div className="text-sm opacity-70">
Canvas placeholder (graph/map). Wire to IntelGraph/H3/Map layers later.
</div>
<div className="mt-3 text-sm">
Reality / Narrative / Belief layers are toggled above; this canvas will render the chosen overlays.
</div>
</div>

<MetricsPanel narratives={narratives} divergence={divergence} onExplain={explain} />

<ExplainDrawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
title={drawerTitle}
body={drawerBody}
disclaimers={drawerDisclaimers}
/>
</div>
);
}
Loading
Loading