feat(dashboard): consistent ViewHeader on Artifacts/Research/Evals/Goals/Memory
Normalize these main-content views onto the shared ViewHeader (icon + CC-style title; existing header controls moved into the actions slot). Tests updated for the new header markup (95 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,33 +9,15 @@
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
The header row now comes from the shared .view-header (which supplies the --space-lg top/side padding). The container only contributes the bottom border + surface; the controls row gets its own side/bottom padding so the tab bar/search stay aligned without doubling the ViewHeader inset.
|
||||
*/
|
||||
.documents-view-header {
|
||||
padding: var(--space-lg);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.documents-view-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.documents-view-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.documents-view-title svg {
|
||||
color: var(--todo);
|
||||
}
|
||||
|
||||
.documents-view-count {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
@@ -46,6 +28,7 @@
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
flex-wrap: nowrap;
|
||||
padding: 0 var(--space-lg) var(--space-lg);
|
||||
}
|
||||
|
||||
.documents-tab-bar {
|
||||
@@ -847,25 +830,12 @@ The artifacts tab is a thumbnail-first responsive media gallery for agent-create
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Documents View mobile */
|
||||
.documents-view-header {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.documents-view-title-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.documents-view-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Documents View mobile: ViewHeader supplies its own responsive padding; only the controls row needs tightening here. */
|
||||
.documents-controls-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-sm);
|
||||
padding: 0 var(--space-md) var(--space-md);
|
||||
}
|
||||
|
||||
.documents-tab-bar {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useSelectionComment } from "../hooks/useSelectionComment";
|
||||
import { SelectionCommentPopover } from "./SelectionCommentPopover";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { ArtifactMedia, getArtifactTypeLabel } from "./ArtifactMedia";
|
||||
import { ViewHeader } from "./ViewHeader";
|
||||
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
@@ -538,17 +539,21 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onSendSelecti
|
||||
|
||||
return (
|
||||
<div className="documents-view">
|
||||
{/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
Documents/Artifacts adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the result count rides in the header actions while the tab bar, hidden-files toggle, and search stay in the controls row below.
|
||||
FNXC:Navigation 2026-06-21-18:25: FN-6890 keeps the top-level title as Artifacts (renamed from Documents) without changing internal task-document tabs or artifact sub-tabs.
|
||||
*/}
|
||||
<div className="documents-view-header">
|
||||
<div className="documents-view-title-row">
|
||||
<h2 className="documents-view-title">
|
||||
<FileText size={20} />
|
||||
{/* FNXC:Navigation 2026-06-21-18:25: FN-6890 renames the top-level Documents view header to Artifacts without changing internal task-document tabs or artifact sub-tabs. */}
|
||||
{t("documents.title", "Artifacts")}
|
||||
</h2>
|
||||
<span className="documents-view-count">
|
||||
{t("documents.resultCount", "{{count}} result{{plural}}", { count: activeCount, plural: activeCount !== 1 ? "s" : "" })}
|
||||
</span>
|
||||
</div>
|
||||
<ViewHeader
|
||||
icon={FileText}
|
||||
title={t("documents.title", "Artifacts")}
|
||||
actions={(
|
||||
<span className="documents-view-count">
|
||||
{t("documents.resultCount", "{{count}} result{{plural}}", { count: activeCount, plural: activeCount !== 1 ? "s" : "" })}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="documents-controls-row">
|
||||
<div className="documents-tab-bar" role="tablist" aria-label={t("documents.sectionsLabel", "Documents sections")}>
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
The shared .view-header sits at the top of the Evals view; the two-column results/detail grid moves into .evals-view__body so the header keeps the standard --space-lg inset while the body retains its side/bottom padding.
|
||||
*/
|
||||
.evals-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.evals-view__body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: var(--space-lg);
|
||||
padding: 0 var(--space-lg) var(--space-lg);
|
||||
}
|
||||
|
||||
.evals-list,
|
||||
@@ -80,8 +90,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.evals-view {
|
||||
.evals-view__body {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0 var(--space-md) var(--space-md);
|
||||
}
|
||||
|
||||
.evals-toolbar {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ExternalLink, RefreshCw, Settings } from "lucide-react";
|
||||
import { ExternalLink, RefreshCw, Settings, Target } from "lucide-react";
|
||||
import { fetchSettings } from "../api";
|
||||
import { useEvals } from "../hooks/useEvals";
|
||||
import type { SectionId } from "./SettingsModal";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { ViewHeader } from "./ViewHeader";
|
||||
import "./EvalsView.css";
|
||||
|
||||
interface EvalsViewProps {
|
||||
@@ -51,7 +52,13 @@ export function EvalsView({ projectId, onOpenSettings, onOpenTaskDetail }: Evals
|
||||
}
|
||||
|
||||
return (
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
Evals adopts the shared ViewHeader (CC-modeled) so this main-content destination reads consistently with the others; the scored-results grid moves into a body wrapper beneath the header. The per-list Refresh control stays in the results toolbar.
|
||||
*/
|
||||
<section className="evals-view" data-testid="evals-view">
|
||||
<ViewHeader icon={Target} title={t("evals.title", "Evals")} />
|
||||
<div className="evals-view__body">
|
||||
<div className="evals-list card">
|
||||
<div className="evals-toolbar">
|
||||
<input
|
||||
@@ -140,6 +147,7 @@ export function EvalsView({ projectId, onOpenSettings, onOpenTaskDetail }: Evals
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,31 +2,28 @@
|
||||
FNXC:GoalsViewStyling 2026-06-20-01:33:
|
||||
FN-6789 mounts Goals as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix.
|
||||
*/
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
The title row now comes from the shared .view-header (which supplies the --space-lg top/side padding). The root keeps scroll + sizing but drops its uniform padding; .goals-view__content carries the body's horizontal/bottom inset and the inter-block gap so cards stay aligned under the header.
|
||||
*/
|
||||
.goals-view {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.goals-header {
|
||||
.goals-view__content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.goals-title {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: calc(var(--space-lg) + var(--space-xs));
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
min-width: 0;
|
||||
padding: 0 var(--space-lg) var(--space-lg);
|
||||
}
|
||||
|
||||
.goals-count {
|
||||
@@ -286,11 +283,7 @@ FN-6828 keeps Goals action controls and linked-mission panels at intrinsic heigh
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.goals-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* .goals-header removed: the shared .view-header wraps its actions cluster on narrow widths. */
|
||||
.goals-card {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Goal } from "@fusion/core";
|
||||
import { Link, Plus, Sparkles, X } from "lucide-react";
|
||||
import { Link, Plus, Sparkles, Target, X } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { draftGoalDescription, getRefineErrorMessage } from "../api";
|
||||
import { ViewHeader } from "./ViewHeader";
|
||||
import "./GoalsView.css";
|
||||
|
||||
export interface GoalsViewProps {
|
||||
@@ -430,18 +431,27 @@ export function GoalsView({ initialGoals, anchorGoalId, onNavigateToMission }: G
|
||||
|
||||
return (
|
||||
<section className="goals-view" data-testid="goals-view">
|
||||
<header className="goals-header">
|
||||
<div>
|
||||
<h2 className="goals-title">{t("goals.title", "Goals")}</h2>
|
||||
<p className="goals-count" data-testid="goals-active-count">
|
||||
{t("goals.activeCount", "{{count}} active goals", { count: activeCount })}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" className="btn btn-primary goals-add-button" onClick={openAddForm} data-testid="goals-add-button">
|
||||
<Plus aria-hidden="true" />
|
||||
{t("goals.addGoal", "Add Goal")}
|
||||
</button>
|
||||
</header>
|
||||
{/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
Goals adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the Add Goal action and the active-goal count both ride in the header actions cluster so existing behavior and the goals-active-count test hook are preserved.
|
||||
*/}
|
||||
<ViewHeader
|
||||
icon={Target}
|
||||
title={t("goals.title", "Goals")}
|
||||
actions={(
|
||||
<>
|
||||
<p className="goals-count" data-testid="goals-active-count">
|
||||
{t("goals.activeCount", "{{count}} active goals", { count: activeCount })}
|
||||
</p>
|
||||
<button type="button" className="btn btn-primary goals-add-button" onClick={openAddForm} data-testid="goals-add-button">
|
||||
<Plus aria-hidden="true" />
|
||||
{t("goals.addGoal", "Add Goal")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
{/* FNXC:Navigation 2026-06-22-01:12: Inner content keeps its own horizontal padding via .goals-view__content so it aligns with the ViewHeader inset after the root drops its uniform padding. */}
|
||||
<div className="goals-view__content">
|
||||
|
||||
{isAddFormOpen ? (
|
||||
<div className="card goals-form" data-testid="goals-form">
|
||||
@@ -692,6 +702,7 @@ export function GoalsView({ initialGoals, anchorGoalId, onNavigateToMission }: G
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
/* === MemoryView === */
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
The title row now comes from the shared .view-header (which supplies the --space-lg top/side padding). The root drops its uniform padding; the description, tab bar, and content area carry their own horizontal inset so they align under the header.
|
||||
*/
|
||||
.memory-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: var(--space-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.memory-view-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.memory-view-header h2 {
|
||||
font-size: 18px;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.memory-view-description {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
margin: var(--space-xs) 0 0 0;
|
||||
margin: 0;
|
||||
padding: 0 var(--space-lg);
|
||||
}
|
||||
|
||||
.memory-view-tabs {
|
||||
@@ -32,7 +22,8 @@
|
||||
flex-direction: row;
|
||||
gap: var(--space-xs);
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: var(--space-lg);
|
||||
margin: var(--space-md) 0 var(--space-lg);
|
||||
padding: 0 var(--space-lg);
|
||||
}
|
||||
|
||||
.memory-view-tab {
|
||||
@@ -67,6 +58,7 @@
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 var(--space-lg) var(--space-lg);
|
||||
}
|
||||
|
||||
.memory-working-tab,
|
||||
@@ -441,13 +433,14 @@
|
||||
|
||||
/* Mobile responsive for memory view */
|
||||
@media (max-width: 768px) {
|
||||
.memory-view {
|
||||
padding: var(--space-md);
|
||||
/* ViewHeader supplies its own responsive padding; the body blocks tighten their horizontal inset here. */
|
||||
.memory-view-description,
|
||||
.memory-view-tabs {
|
||||
padding-inline: var(--space-md);
|
||||
}
|
||||
|
||||
.memory-view-header {
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
.memory-view-content {
|
||||
padding: 0 var(--space-md) var(--space-md);
|
||||
}
|
||||
|
||||
.memory-editor-container {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Brain, Loader2 } from "lucide-react";
|
||||
import "./MemoryView.css";
|
||||
import "./SettingsModal.css";
|
||||
import type { MemoryFileInfo, MemoryRetrievalTestResult } from "../api";
|
||||
import { FileEditor } from "./FileEditor";
|
||||
import { ViewHeader } from "./ViewHeader";
|
||||
import { useMemoryData } from "../hooks/useMemoryData";
|
||||
|
||||
interface MemoryViewProps {
|
||||
@@ -349,15 +350,14 @@ export function MemoryView({ projectId, addToast, onSendSelectionToTask }: Memor
|
||||
|
||||
return (
|
||||
<div className="memory-view">
|
||||
{/* Header */}
|
||||
<div className="memory-view-header">
|
||||
<div>
|
||||
<h2>{t("memory.title", "Memory")}</h2>
|
||||
<p className="memory-view-description">
|
||||
{t("memory.description", "Working memory, long-term insights, and engine status")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
Memory adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the prior description renders just below the header, and the Working/Insights/Engines tab bar stays beneath it.
|
||||
*/}
|
||||
<ViewHeader icon={Brain} title={t("memory.title", "Memory")} />
|
||||
<p className="memory-view-description">
|
||||
{t("memory.description", "Working memory, long-term insights, and engine status")}
|
||||
</p>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="memory-view-tabs" role="tablist">
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
FNXC:ResearchViewStyling 2026-06-20-01:33:
|
||||
FN-6789 mounts Research as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix.
|
||||
*/
|
||||
/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
The title row now comes from the shared .view-header, which supplies the --space-lg top/side padding. The root drops its top/side inset (keeping a bottom inset) and the remaining body blocks (subtitle, layout, setup state) carry their own horizontal padding so they stay aligned with the header.
|
||||
*/
|
||||
.research-view {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
@@ -12,31 +16,19 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding: var(--space-lg);
|
||||
padding-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.research-view__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.research-view__header-actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.research-view__title {
|
||||
margin: 0;
|
||||
padding: 0 0 var(--space-lg);
|
||||
}
|
||||
|
||||
.research-view__subtitle {
|
||||
margin: var(--space-xs) 0 0;
|
||||
margin: 0;
|
||||
padding-inline: var(--space-lg);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.research-view__state {
|
||||
margin-inline: var(--space-lg);
|
||||
}
|
||||
|
||||
.research-view__layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
||||
@@ -44,6 +36,7 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding-inline: var(--space-lg);
|
||||
}
|
||||
|
||||
.research-view__sidebar,
|
||||
@@ -256,10 +249,18 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: var(--space-md);
|
||||
padding: 0;
|
||||
padding-bottom: calc(var(--space-md) + var(--mobile-nav-height) + env(safe-area-inset-bottom, 0px) + var(--standalone-bottom-gap));
|
||||
}
|
||||
|
||||
.research-view__subtitle {
|
||||
padding-inline: var(--space-md);
|
||||
}
|
||||
|
||||
.research-view__state {
|
||||
margin-inline: var(--space-md);
|
||||
}
|
||||
|
||||
.research-view__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -268,6 +269,7 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
|
||||
flex: initial;
|
||||
min-height: auto;
|
||||
overflow: visible;
|
||||
padding-inline: var(--space-md);
|
||||
}
|
||||
|
||||
.research-view__sidebar,
|
||||
@@ -284,14 +286,6 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.research-view__header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.research-view__header-actions {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.research-view__stats {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useResearch } from "../hooks/useResearch";
|
||||
import type { ResearchProviderOption } from "../research-types";
|
||||
import { ResearchTaskActionModal } from "./ResearchTaskActionModal";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { ViewHeader } from "./ViewHeader";
|
||||
import type { SectionId } from "./SettingsModal";
|
||||
import "./ResearchView.css";
|
||||
import { recordResumeEvent } from "../utils/resumeInstrumentation";
|
||||
@@ -252,17 +253,20 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
|
||||
|
||||
return (
|
||||
<section className="research-view" aria-label={t("research.viewLabel", "Research view")}>
|
||||
<header className="research-view__header">
|
||||
<div>
|
||||
<h2 className="research-view__title">{t("research.title", "Research")}</h2>
|
||||
<p className="research-view__subtitle">{t("research.subtitle", "Cited search and synthesis runs: gather sources, fetch content, and synthesize findings.")}</p>
|
||||
</div>
|
||||
<div className="research-view__header-actions">
|
||||
{/*
|
||||
FNXC:Navigation 2026-06-22-01:10:
|
||||
Research adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the Refresh action moves into the header actions cluster and the prior subtitle renders just below the header so the descriptive copy is preserved.
|
||||
*/}
|
||||
<ViewHeader
|
||||
icon={Search}
|
||||
title={t("research.title", "Research")}
|
||||
actions={(
|
||||
<button className="btn" type="button" onClick={() => void refresh()}>
|
||||
{t("actions.refresh", "Refresh")}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
/>
|
||||
<p className="research-view__subtitle">{t("research.subtitle", "Cited search and synthesis runs: gather sources, fetch content, and synthesize findings.")}</p>
|
||||
|
||||
{setupState ? (
|
||||
<div className="research-view__state research-view__state--error card" data-testid="research-state-unavailable">
|
||||
|
||||
@@ -14,6 +14,8 @@ vi.mock("lucide-react", () => ({
|
||||
Link: () => <span data-testid="icon-link" />,
|
||||
Plus: () => <span data-testid="icon-plus" />,
|
||||
Sparkles: () => <span data-testid="icon-sparkles" />,
|
||||
// Target backs the shared ViewHeader icon for the Goals view header (FNXC:Navigation 2026-06-22).
|
||||
Target: () => <span data-testid="icon-target" />,
|
||||
X: () => <span data-testid="icon-x" />,
|
||||
}));
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ vi.mock("../FileEditor", () => ({
|
||||
|
||||
vi.mock("lucide-react", () => ({
|
||||
Loader2: () => <span data-testid="loader-icon" />,
|
||||
// Brain backs the shared ViewHeader icon for the Memory view header (FNXC:Navigation 2026-06-22).
|
||||
Brain: () => <span data-testid="icon-brain" />,
|
||||
}));
|
||||
|
||||
function createMemoryData(overrides: Record<string, unknown> = {}) {
|
||||
|
||||
Reference in New Issue
Block a user