fix(mesh): state what is shared plainly, and scope the memory meter

Drops two captions that hedged or mislabeled facts we have.

"Join to see live detail" was wrong twice over: in that state the only thing to
join is sharing, and the field already shows what the community has published.
"Last reported by the community" annotated a real fact with doubt -- the notes
are current within 120s, and what we know is shared should be shown as shared.
The live-vs-relay difference is already carried by the geometry: relay nodes get
no synthetic RTT, and the centre is hollow with no spokes because adjacency is
what we cannot assert. A caption repeating that was noise.

The only surviving caption states something the drawing cannot: alone on the
mesh.

The memory meter is per-node and now says so -- "This computer · model uses 10
GB of 40 GB". It sits directly beneath a radar field showing the whole
community, so an unqualified figure read as mesh-wide utilization: a number
nobody has, since members publish their own capacity but nothing about each
other's live allocation. A test pins the scoping rather than the wording.

Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Michael Neale
2026-08-05 12:51:57 +10:00
parent 2a2977795a
commit d23e3cf237
4 changed files with 68 additions and 21 deletions
@@ -84,7 +84,14 @@ test("relay nodes carry no RTT and claim no membership", () => {
// A hollow centre and no spokes depend on this staying false.
assert.equal(field.selfParticipating, false);
assert.equal(field.selfCapacityGb, null);
assert.match(field.caption, /Last reported/);
});
test("the relay view is not hedged with a caption", () => {
// What we know is shared gets shown as shared. Annotating it with "last
// reported" hedged a fact we actually have, and the old "join to see live
// detail" mislabeled sharing as joining.
const field = deriveMeshFieldModel({ view: null, snapshot: snapshot() });
assert.equal(field.caption, null);
});
test("our own note is excluded so we are never drawn twice", () => {
@@ -132,5 +139,5 @@ test("a solo sharer is told it is waiting, not that it is alone in error", () =>
snapshot: null,
});
assert.equal(field.source, "live");
assert.match(field.caption, /waiting for another device/);
assert.equal(field.caption, "Waiting for another device");
});
@@ -13,14 +13,16 @@ import type { MeshFieldNode } from "./ui/MeshRadarField";
* renders either way, and the earlier "turn on sharing to see who's on the
* mesh" was simply wrong.
*
* The two are not equivalent, and the difference is not cosmetic:
* The two are not equivalent, and the difference is expressed in the geometry
* rather than in a hedging caption — what we know is shared gets shown either
* way, and the drawing simply does not assert more than it can:
*
* - **live**: reachable now, RTT known, adjacency to us is real
* - **relay**: last known within 120s, no RTT, adjacency unknown
* - **relay**: published within the last 120s, no RTT, adjacency unknown
*
* So relay nodes get no RTT (they land mid-band rather than faking proximity),
* the caller draws no spokes when we have not joined, and the caption names
* which view is on screen. Never dress last-known state as live.
* and the caller draws no spokes and a hollow centre when we have not joined,
* because a spoke would assert a connection we do not have.
*/
export type MeshFieldSource = "live" | "relay" | "none";
@@ -38,7 +40,13 @@ export type MeshFieldModel = {
* so ghosts never contribute capacity — they are the invitation, not a total.
*/
ghostCount: number;
/** Caption naming the source, or null when there is nothing to qualify. */
/**
* Short caption, or null.
*
* Only ever states a fact the field cannot draw — currently just "alone on
* the mesh". Never used to hedge the relay view: what we know is shared is
* shown as shared, not annotated with doubt.
*/
caption: string | null;
};
@@ -68,10 +76,7 @@ export function deriveMeshFieldModel({
selfParticipating: true,
selfCapacityGb: view.selfCapacityGb,
ghostCount,
caption:
view.peers.length === 0
? "Connected · waiting for another device"
: null,
caption: view.peers.length === 0 ? "Waiting for another device" : null,
};
}
@@ -103,6 +108,6 @@ export function deriveMeshFieldModel({
selfParticipating: false,
selfCapacityGb: null,
ghostCount,
caption: "Last reported by the community · join to see live detail",
caption: null,
};
}
@@ -33,7 +33,7 @@ test("prefers the runtime's reported model footprint", () => {
catalog,
modelRef: "gemma",
});
assert.equal(model.label, "10 GB of 40 GB AI memory used by model");
assert.equal(model.label, "This computer · model uses 10 GB of 40 GB");
assert.equal(model.usedSegments, 3);
});
@@ -43,7 +43,7 @@ test("falls back to the selected catalog model before runtime status arrives", (
catalog,
modelRef: "gemma",
});
assert.equal(model.label, "8 GB of 32 GB AI memory used by model");
assert.equal(model.label, "This computer · model uses 8 GB of 32 GB");
assert.equal(model.usedSegments, 3);
});
@@ -53,6 +53,32 @@ test("unknown model size shows available memory without inventing use", () => {
catalog,
modelRef: "custom/model",
});
assert.equal(model.label, "32 GB AI memory available");
assert.equal(model.label, "This computer · 32 GB AI memory");
assert.equal(model.usedSegments, 0);
});
test("every label names this computer, never the mesh", () => {
// The meter sits directly under a radar field showing the whole community.
// An unqualified "10 of 40 GB" would read as mesh-wide utilization, which is
// a number nobody has: members publish their own capacity, not each other's
// live allocation.
const cases = [
{
view: {
connected: true,
selfCapacityGb: 40,
selfModelSizeGb: 10,
peers: [],
},
catalog,
modelRef: "gemma",
},
{ view: null, catalog, modelRef: "gemma" },
{ view: null, catalog, modelRef: "custom/model" },
{ view: null, catalog: null, modelRef: null },
];
for (const input of cases) {
const model = deriveMeshMemoryModel(input);
assert.match(model.label, /this computer/i, model.label);
}
});
@@ -11,9 +11,16 @@ export type MeshMemoryModel = {
};
/**
* Approximate model footprint against this machine's shareable AI memory.
* MeshLLM reports model package size, not live OS GPU allocation, so the UI
* deliberately says "model uses" rather than claiming real-time utilization.
* Approximate model footprint against **this machine's** shareable AI memory.
*
* Scoped to this node on purpose, and the label says so. It sits directly under
* a radar field showing the whole community, so an unqualified "36 of 115 GB"
* would read as mesh-wide utilization — a number we do not have and could not
* compute, since members publish their own capacity but nothing about each
* other's live allocation.
*
* MeshLLM reports model package size, not live OS GPU allocation, so the label
* says the model "uses" rather than claiming real-time utilization.
*/
export function deriveMeshMemoryModel({
view,
@@ -47,12 +54,14 @@ export function deriveMeshMemoryModel({
usedGb,
usedSegments,
totalSegments: MEMORY_SEGMENTS,
// "This computer" leads, because the meter sits under a community-wide
// radar field and must not be read as mesh utilization.
label:
usedGb != null && totalGb != null
? `${formatGb(usedGb)} of ${formatGb(totalGb)} AI memory used by model`
? `This computer · model uses ${formatGb(usedGb)} of ${formatGb(totalGb)}`
: totalGb != null
? `${formatGb(totalGb)} AI memory available`
: "Checking available AI memory…",
? `This computer · ${formatGb(totalGb)} AI memory`
: "Checking this computer's AI memory…",
};
}