Fix world-map zoom-out starfield, tree search canvas hits, and sidebar scroll.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-07 14:23:21 +08:00
parent fdab188cc1
commit 467079d6ca
3 changed files with 140 additions and 38 deletions

View file

@ -4494,10 +4494,11 @@ pre {
max-height: min(360px, 50vh);
overflow: auto;
padding: 4px;
border: 1px solid #b8c4d2;
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: 8px;
background: #fff;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
background: #0b1220;
color: #e2e8f0;
box-shadow: 0 8px 24px rgba(2, 8, 23, 0.55);
}
.topo-tree-search__item {
@ -4510,7 +4511,7 @@ pre {
.topo-tree-search__item + .topo-tree-search__item {
margin-top: 2px;
border-top: 1px solid #eef2f6;
border-top: 1px solid rgba(148, 163, 184, 0.16);
padding-top: 4px;
}
@ -4530,14 +4531,14 @@ pre {
}
.topo-tree-search__ne:hover {
background: #f1f5f9;
background: rgba(148, 163, 184, 0.12);
}
.topo-tree-search__name {
grid-column: 1;
font-size: 12px;
font-weight: 600;
color: var(--nm-ink, #0f172a);
color: #e2e8f0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -4566,11 +4567,11 @@ pre {
.topo-tree-search__view {
max-width: 100%;
padding: 2px 8px;
border: 1px solid #d0dae6;
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: 999px;
background: #f8fafc;
background: rgba(30, 41, 59, 0.9);
font-size: 11px;
color: var(--nm-brand, #1d4ed8);
color: #93c5fd;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
@ -4578,8 +4579,8 @@ pre {
}
.topo-tree-search__view:hover {
background: #e8eef8;
border-color: #9eb6d8;
background: rgba(51, 65, 85, 0.95);
border-color: rgba(96, 165, 250, 0.45);
}
.topo-tree-search__hint {
@ -5561,12 +5562,29 @@ pre {
flex-direction: column;
gap: 2px;
padding: 2px 0;
/* Allow deep indent + actions to extend; parent scroll shows a horizontal bar. */
width: max-content;
min-width: 100%;
max-width: none;
box-sizing: border-box;
}
.topo-region-list-scroll {
flex: 1 1 auto;
min-height: 0;
min-width: 0;
overflow-x: auto;
overflow-y: auto;
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: 6px;
background: rgba(2, 8, 23, 0.45);
padding: 2px 0;
}
.topo-region-list__block {
margin: 0;
width: auto;
max-width: 100%;
max-width: none;
min-width: 0;
padding: 2px 0 4px;
box-sizing: border-box;
@ -5585,6 +5603,8 @@ pre {
overflow: visible;
gap: 2px;
padding-right: 2px;
width: max-content;
min-width: 100%;
transition: background 0.12s ease;
}
@ -5691,6 +5711,7 @@ pre {
.topo-region-list .topo-map-list__actions {
opacity: 0;
pointer-events: none;
flex: 0 0 auto;
border-left: 0;
gap: 4px;
padding-right: 4px;
@ -5730,10 +5751,10 @@ pre {
}
.topo-region-list .topo-map-list__title {
min-width: 0;
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
min-width: max-content;
flex: 0 1 auto;
overflow: visible;
text-overflow: clip;
white-space: nowrap;
font-size: 13px;
font-weight: 650;

View file

@ -276,11 +276,23 @@ function mergeFlatWorldGraph(
next: TopologyViewGraph,
opts?: { centerX?: number; centerY?: number },
): TopologyViewGraph {
if (!prev?.nodes?.length) return next;
const scatter = next.scatter?.length ? next.scatter : prev?.scatter;
if (!prev?.nodes?.length) {
return scatter?.length && !next.scatter?.length ? { ...next, scatter } : next;
}
// Overview / zoom-out payloads have empty RF nodes — keep (or refresh) scatter,
// drop heavy tiles when the server intentionally cleared them.
if (!next?.nodes?.length) {
return {
...prev,
view: next.view || prev.view,
nodes: [],
edges: [],
scatter: scatter || [],
world_transform: next.world_transform ?? prev.world_transform,
truncated: Boolean(next.truncated || prev.truncated),
truncate_reason: next.truncate_reason || prev.truncate_reason || "",
outside_peers: next.outside_peers?.length ? next.outside_peers : prev.outside_peers,
};
}
@ -320,7 +332,7 @@ function mergeFlatWorldGraph(
nodes,
edges,
world_transform: next.world_transform ?? prev.world_transform,
scatter: next.scatter?.length ? next.scatter : prev.scatter,
scatter: scatter || [],
truncated: Boolean(prev.truncated || next.truncated),
truncate_reason: next.truncate_reason || prev.truncate_reason || "",
outside_peers: next.outside_peers?.length ? next.outside_peers : prev.outside_peers,
@ -1428,12 +1440,14 @@ export function TopologyPage() {
const cx = bounds ? bounds.x + bounds.width / 2 : 0;
const cy = bounds ? bounds.y + bounds.height / 2 : 0;
commit(g, cx, cy);
} else if (prev.nodes.length > 0 && zoom < 0.04) {
} else if (prev.nodes.length > 0) {
// Always shed RF tiles when leaving close-up so scatter is the sole layer.
commit(
{
...prev,
nodes: [],
edges: [],
scatter: prev.scatter,
world_transform: prev.world_transform
? { ...prev.world_transform, lod: "overview" }
: prev.world_transform,
@ -4156,7 +4170,7 @@ export function TopologyPage() {
</span>
)}
</button>
{views.length > 1 ? (
{views.length > 0 ? (
<div className="topo-tree-search__views">
{views.map((v) => (
<button
@ -4166,7 +4180,9 @@ export function TopologyPage() {
onClick={() => jumpToTreeSearchHit(hit, v)}
title={`${v.folder_name ? `${v.folder_name} / ` : ""}${v.view_name}`}
>
{v.view_name || v.view_id.slice(0, 8)}
{v.folder_name
? `${v.folder_name} / ${v.view_name || v.view_id.slice(0, 8)}`
: v.view_name || v.view_id.slice(0, 8)}
</button>
))}
</div>
@ -4214,9 +4230,11 @@ export function TopologyPage() {
) : regions.length === 0 ? (
<p className="panel__hint">{t("topology.emptyMaps")}</p>
) : (
<ul className="topo-map-list topo-region-list">
{regions.map((region) => renderWorldNavFolder(region, 0))}
</ul>
<div className="topo-region-list-scroll">
<ul className="topo-map-list topo-region-list">
{regions.map((region) => renderWorldNavFolder(region, 0))}
</ul>
</div>
)}
{mapId && outsidePeers.length > 0 && (
<div className="topo-outside-peers">