Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c645453
fix(dashboard): prioritize home orb live overview signals
gdemonc May 6, 2026
4333566
fix(dashboard): balance home orb summon signals
gdemonc May 6, 2026
00742cb
fix(dashboard): align home mirror copy with overview
gdemonc May 6, 2026
2cdb9e4
fix(dashboard): drop fake empty-note home copy
gdemonc May 6, 2026
e1f89b0
fix(dashboard): align home notes copy with notepad buckets
gdemonc May 6, 2026
f06f2d6
fix(dashboard): align remaining home orb module copy
gdemonc May 6, 2026
8710ee0
fix(dashboard): warn on home mirror overview gaps
gdemonc May 6, 2026
6d466ff
fix(dashboard): ignore closed-only note orbs
gdemonc May 6, 2026
50921d2
fix(dashboard): align safety and mirror home orbs
gdemonc May 6, 2026
917ae58
fix(dashboard): keep first home orb priority ordered
gdemonc May 6, 2026
8177a17
fix(dashboard): align home orb task CTA targets
gdemonc May 6, 2026
4e990bf
fix(dashboard): humanize home mirror orb copy
gdemonc May 7, 2026
3bfe49d
fix(dashboard): rotate home mirror orb sources
gdemonc May 7, 2026
1631f02
fix(dashboard): dedupe home event panel copy
gdemonc May 8, 2026
e6cd46c
fix(dashboard): close expanded home panel on escape
gdemonc May 9, 2026
22190f2
fix(dashboard): align orb CTA with task route
xgopilot May 10, 2026
575501e
fix(dashboard): sanitize mirror home orb copy
xgopilot May 10, 2026
98c3761
fix(dashboard): align orb summon routing copy
xgopilot May 10, 2026
059f47c
Merge pull request #13 from gdemonc/fork-pr-435-1778389108
gdemonc May 10, 2026
37582e4
fix(dashboard): align orb fallback routing
xgopilot May 10, 2026
3c8f3ee
Merge pull request #15 from gdemonc/fork-pr-435-1778389108
gdemonc May 10, 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
143 changes: 130 additions & 13 deletions apps/desktop/src/app/dashboard/DashboardHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,90 @@ function getCenterState(activeStateKey: DashboardHomeEventStateKey | null) {
return "hover_input" as const;
}

function pickNextSummonIndex(
templates: Array<Omit<DashboardHomeSummonEvent, "id">>,
previousIndex: number,
previousModule: DashboardHomeModuleKey | null,
) {
const total = templates.length;
if (total <= 1) {
return 0;
}

// Keep the very first visible orb aligned with the service-side priority
// ordering so urgent overview signals are not randomized behind softer copy.
if (previousIndex < 0 || previousModule === null) {
return 0;
}

const candidateIndexes = templates
.map((template, index) => ({ index, module: template.module }))
.filter((candidate) => candidate.index !== previousIndex && candidate.module !== previousModule)
.map((candidate) => candidate.index);

const fallbackIndexes = templates
.map((_, index) => index)
.filter((index) => index !== previousIndex);

const pool = candidateIndexes.length > 0 ? candidateIndexes : fallbackIndexes;
const nextIndex = pool[Math.floor(Math.random() * pool.length)];
if (nextIndex !== previousIndex) {
return nextIndex;
}

return (nextIndex + 1) % total;
}

function buildSummonTemplateSignature(templates: Array<Omit<DashboardHomeSummonEvent, "id">>) {
const buildNavigationTargetSignature = (
target: DashboardHomeSummonEvent["expandedState"]["navigationTarget"] | undefined,
) => {
if (!target) {
return "";
}

if (target.kind === "task_detail") {
return [
target.kind,
target.module,
target.label,
target.taskId,
].join("::");
}

if (target.kind === "mirror_detail") {
return [
target.kind,
target.module,
target.label,
target.activeDetailKey,
target.focusMemoryId ?? "",
].join("::");
}

return [
target.kind,
target.module,
target.label,
].join("::");
};

return templates
.map((template) => [
template.stateKey,
template.module,
template.message,
template.reason,
template.nextStep ?? "",
template.priority,
template.recommendationId ?? "",
template.expandedState?.headline ?? "",
template.expandedState?.subline ?? "",
buildNavigationTargetSignature(template.expandedState?.navigationTarget),
].join("::"))
.join("||");
}

type DashboardHomeProps = {
data: DashboardHomeData;
onVoiceOpen: () => void;
Expand All @@ -68,12 +152,16 @@ export function DashboardHome({
const [orbDragOffset, setOrbDragOffset] = useState({ x: 0, y: 0 });
const [hoveredEntranceKey, setHoveredEntranceKey] = useState<string | null>(null);
const [activeStateKey, setActiveStateKey] = useState<DashboardHomeEventStateKey | null>(null);
const [activeExpandedState, setActiveExpandedState] = useState<DashboardHomeSummonEvent["expandedState"] | null>(null);
const [summons, setSummons] = useState<DashboardHomeSummonEvent[]>([]);
const summonIndexRef = useRef(0);
const summonIdRef = useRef(0);
const lastSummonIndexRef = useRef(-1);
const lastSummonModuleRef = useRef<DashboardHomeModuleKey | null>(null);
const summonTimerRef = useRef<number | null>(null);
const summonTemplatesRef = useRef(data.summonTemplates);
const summonTemplateSignature = buildSummonTemplateSignature(data.summonTemplates);

const activeState = activeStateKey ? data.stateMap[activeStateKey] : null;
const activeState = activeExpandedState ?? (activeStateKey ? data.stateMap[activeStateKey] : null);
const activeModule = hoveredEntranceKey
? dashboardEntranceOrbs.find((config) => config.key === hoveredEntranceKey)?.module ?? activeState?.module ?? null
: activeState?.module ?? null;
Expand All @@ -82,13 +170,27 @@ export function DashboardHome({
const currentReasonLine = activeState?.subline ?? summons[0]?.reason ?? data.focusLine.reason;
const isOverlayOpen = Boolean(activeState || voiceOpen);

const closeActiveOverlay = useCallback(() => {
setActiveExpandedState(null);
setActiveStateKey(null);
}, []);

const scheduleSummon = useCallback(() => {
if (data.summonTemplates.length === 0) {
const templates = summonTemplatesRef.current;
if (templates.length === 0) {
return;
}

const template = data.summonTemplates[summonIndexRef.current % data.summonTemplates.length];
summonIndexRef.current += 1;
// Summons are local presentation state, so balancing the module order here
// does not change the formal dashboard data contract or backend ranking.
const nextIndex = pickNextSummonIndex(
templates,
lastSummonIndexRef.current,
lastSummonModuleRef.current,
);
lastSummonIndexRef.current = nextIndex;
const template = templates[nextIndex];
lastSummonModuleRef.current = template.module;

setSummons((current) => {
if (current.length >= 1) {
Expand All @@ -106,11 +208,16 @@ export function DashboardHome({

const gap = (template.duration ?? 5_000) + 7_000;
summonTimerRef.current = window.setTimeout(scheduleSummon, gap);
}, []);

useEffect(() => {
summonTemplatesRef.current = data.summonTemplates;
}, [data.summonTemplates]);

useEffect(() => {
summonIndexRef.current = 0;
summonIdRef.current = 0;
lastSummonIndexRef.current = -1;
lastSummonModuleRef.current = null;
setSummons([]);

if (data.summonTemplates.length === 0) {
Expand All @@ -124,7 +231,7 @@ export function DashboardHome({
window.clearTimeout(summonTimerRef.current);
}
};
}, [data.summonTemplates.length, scheduleSummon]);
}, [data.summonTemplates.length, scheduleSummon, summonTemplateSignature]);

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
Expand All @@ -134,15 +241,15 @@ export function DashboardHome({
return;
}

if (event.key === "Escape" && activeStateKey) {
if (event.key === "Escape" && (activeStateKey || activeExpandedState)) {
event.preventDefault();
setActiveStateKey(null);
closeActiveOverlay();
}
};

window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [activeStateKey]);
}, [activeExpandedState, activeStateKey, closeActiveOverlay]);

const centerVisualState = voiceOpen ? "voice_locked" : getCenterState(activeStateKey);
const pageStyle = {
Expand Down Expand Up @@ -260,8 +367,9 @@ export function DashboardHome({
onRecommendationFeedback?.(event.recommendationId, "negative");
}
}}
onExpand={(stateKey) => {
setActiveStateKey(stateKey);
onExpand={(expandedEvent) => {
setActiveStateKey(expandedEvent.stateKey);
setActiveExpandedState(expandedEvent.expandedState ?? null);
if (event.recommendationId) {
onRecommendationFeedback?.(event.recommendationId, "positive");
}
Expand All @@ -285,7 +393,16 @@ export function DashboardHome({
</div>
</div>

<DashboardEventPanel activeState={activeState} onClose={() => setActiveStateKey(null)} onStateChange={setActiveStateKey} stateGroups={data.stateGroups} stateMap={data.stateMap} />
<DashboardEventPanel
activeState={activeState}
onClose={closeActiveOverlay}
onStateChange={(stateKey) => {
setActiveExpandedState(null);
setActiveStateKey(stateKey);
}}
stateGroups={data.stateGroups}
stateMap={data.stateMap}
/>
</ClickSpark>
);
}
Loading
Loading