mirror of
https://github.com/samvallad33/vestige.git
synced 2026-04-25 00:36:22 +02:00
feat(graph): redesign node labels as dark glass pills
Some checks are pending
CI / Test (macos-latest) (push) Waiting to run
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Release Build (aarch64-apple-darwin) (push) Blocked by required conditions
CI / Release Build (x86_64-unknown-linux-gnu) (push) Blocked by required conditions
Test Suite / Unit Tests (push) Waiting to run
Test Suite / MCP E2E Tests (push) Waiting to run
Test Suite / User Journey Tests (push) Blocked by required conditions
Test Suite / Dashboard Build (push) Waiting to run
Test Suite / Code Coverage (push) Waiting to run
Some checks are pending
CI / Test (macos-latest) (push) Waiting to run
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Release Build (aarch64-apple-darwin) (push) Blocked by required conditions
CI / Release Build (x86_64-unknown-linux-gnu) (push) Blocked by required conditions
Test Suite / Unit Tests (push) Waiting to run
Test Suite / MCP E2E Tests (push) Waiting to run
Test Suite / User Journey Tests (push) Blocked by required conditions
Test Suite / Dashboard Build (push) Waiting to run
Test Suite / Code Coverage (push) Waiting to run
Labels previously rendered as near-white text (#e2e8f0) on a transparent canvas. UnrealBloomPass (threshold 0.2) amplified every bright pixel into a huge white halo that made labels unreadable at normal camera distances — reported by Sam 2026-04-19 with a screenshot of the LoRA training label blown out into a luminous blob. New design: - Dark rounded pill (rgba(10,16,28,0.82)) sits below the text and hugs its measured width. That keeps the pill background well below bloom threshold so the halo can't spread past the label footprint. - Text dimmed to mid-luminance slate (#94a3b8). Still legible at the standard camera distance but dim enough that bloom only adds a soft glow instead of a blast. - Font trimmed to 22px / weight 600 (was bold 28px); sprite scale tightened from 12×1.5 to 9×1.2 so labels don't visually out-compete the node spheres they annotate. - Hairline slate stroke (18% alpha) on the pill for definition when the camera gets close. The canvas mock in the vitest setup grew beginPath / closePath / moveTo / lineTo / quadraticCurveTo / arc / fill / stroke / strokeText stubs so createTextSprite can exercise the full rounded-rect path in unit tests without a real DOM. All 251 tests stay green.
This commit is contained in:
parent
d7f0fe03e0
commit
30d92b5371
50 changed files with 107 additions and 46 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
1
apps/dashboard/build/_app/immutable/chunks/CK5Nmlyf.js
Normal file
1
apps/dashboard/build/_app/immutable/chunks/CK5Nmlyf.js
Normal file
File diff suppressed because one or more lines are too long
BIN
apps/dashboard/build/_app/immutable/chunks/CK5Nmlyf.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/chunks/CK5Nmlyf.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/chunks/CK5Nmlyf.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/chunks/CK5Nmlyf.js.gz
Normal file
Binary file not shown.
1
apps/dashboard/build/_app/immutable/chunks/DUtaznkq.js
Normal file
1
apps/dashboard/build/_app/immutable/chunks/DUtaznkq.js
Normal file
File diff suppressed because one or more lines are too long
BIN
apps/dashboard/build/_app/immutable/chunks/DUtaznkq.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/chunks/DUtaznkq.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/chunks/DUtaznkq.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/chunks/DUtaznkq.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
apps/dashboard/build/_app/immutable/entry/app.B1RqXwG0.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/entry/app.B1RqXwG0.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/entry/app.B1RqXwG0.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/entry/app.B1RqXwG0.js.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
import{a as r}from"../chunks/BOu53idK.js";import{w as t}from"../chunks/UvrLlSZu.js";export{t as load_css,r as start};
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
import{a as r}from"../chunks/CK5Nmlyf.js";import{w as t}from"../chunks/DUtaznkq.js";export{t as load_css,r as start};
|
||||||
BIN
apps/dashboard/build/_app/immutable/entry/start.C8fl2m83.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/entry/start.C8fl2m83.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/entry/start.C8fl2m83.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/entry/start.C8fl2m83.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
apps/dashboard/build/_app/immutable/nodes/0.Bfsm2nvh.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/0.Bfsm2nvh.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/nodes/0.Bfsm2nvh.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/0.Bfsm2nvh.js.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
import"../chunks/Bzak7iHL.js";import"../chunks/CrlWs-6R.js";import{p as h,f as g,t as d,a as l,d as v,e as s,r as o}from"../chunks/VE8Jor13.js";import{s as p}from"../chunks/DHnEMX8z.js";import{a as _,f as x}from"../chunks/7UNxJI5L.js";import{i as $}from"../chunks/jyeIy8pa.js";import{p as m}from"../chunks/UvrLlSZu.js";import{s as k}from"../chunks/BOu53idK.js";const b={get error(){return m.error},get status(){return m.status}};k.updated.check;const i=b;var E=x("<h1> </h1> <p> </p>",1);function D(f,n){h(n,!1),$();var t=E(),r=g(t),c=s(r,!0);o(r);var a=v(r,2),u=s(a,!0);o(a),d(()=>{var e;p(c,i.status),p(u,(e=i.error)==null?void 0:e.message)}),_(f,t),l()}export{D as component};
|
import"../chunks/Bzak7iHL.js";import"../chunks/CrlWs-6R.js";import{p as h,f as g,t as d,a as l,d as v,e as s,r as o}from"../chunks/VE8Jor13.js";import{s as p}from"../chunks/DHnEMX8z.js";import{a as _,f as x}from"../chunks/7UNxJI5L.js";import{i as $}from"../chunks/jyeIy8pa.js";import{p as m}from"../chunks/DUtaznkq.js";import{s as k}from"../chunks/CK5Nmlyf.js";const b={get error(){return m.error},get status(){return m.status}};k.updated.check;const i=b;var E=x("<h1> </h1> <p> </p>",1);function D(f,n){h(n,!1),$();var t=E(),r=g(t),c=s(r,!0);o(r);var a=v(r,2),u=s(a,!0);o(a),d(()=>{var e;p(c,i.status),p(u,(e=i.error)==null?void 0:e.message)}),_(f,t),l()}export{D as component};
|
||||||
BIN
apps/dashboard/build/_app/immutable/nodes/1.ClSH3vNb.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/1.ClSH3vNb.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/nodes/1.ClSH3vNb.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/1.ClSH3vNb.js.gz
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
import"../chunks/Bzak7iHL.js";import"../chunks/CrlWs-6R.js";import{o as p}from"../chunks/DWVWfZUn.js";import{p as r,a as t}from"../chunks/VE8Jor13.js";import{i as a}from"../chunks/jyeIy8pa.js";import{g as m}from"../chunks/BOu53idK.js";function u(i,o){r(o,!1),p(()=>m("/graph",{replaceState:!0})),a(),t()}export{u as component};
|
import"../chunks/Bzak7iHL.js";import"../chunks/CrlWs-6R.js";import{o as p}from"../chunks/DWVWfZUn.js";import{p as r,a as t}from"../chunks/VE8Jor13.js";import{i as a}from"../chunks/jyeIy8pa.js";import{g as m}from"../chunks/CK5Nmlyf.js";function u(i,o){r(o,!1),p(()=>m("/graph",{replaceState:!0})),a(),t()}export{u as component};
|
||||||
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/nodes/3.BbrO3ed8.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/3.BbrO3ed8.js.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
apps/dashboard/build/_app/immutable/nodes/6.QRT_dh4Q.js.br
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/6.QRT_dh4Q.js.br
Normal file
Binary file not shown.
BIN
apps/dashboard/build/_app/immutable/nodes/6.QRT_dh4Q.js.gz
Normal file
BIN
apps/dashboard/build/_app/immutable/nodes/6.QRT_dh4Q.js.gz
Normal file
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
{"version":"1776650393863"}
|
{"version":"1776653229849"}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -11,13 +11,13 @@
|
||||||
<link rel="icon" type="image/svg+xml" href="/dashboard/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/dashboard/favicon.svg" />
|
||||||
<link rel="apple-touch-icon" href="/dashboard/favicon.svg" />
|
<link rel="apple-touch-icon" href="/dashboard/favicon.svg" />
|
||||||
<link rel="manifest" href="/dashboard/manifest.json" />
|
<link rel="manifest" href="/dashboard/manifest.json" />
|
||||||
<link href="/dashboard/_app/immutable/entry/start.BieeVrE-.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/entry/start.C8fl2m83.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/BOu53idK.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/CK5Nmlyf.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/VE8Jor13.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/VE8Jor13.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/CCRrbKqn.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/CCRrbKqn.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/UvrLlSZu.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/DUtaznkq.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/DWVWfZUn.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/DWVWfZUn.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/entry/app.hiopGwi-.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/entry/app.B1RqXwG0.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/DHnEMX8z.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/DHnEMX8z.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/7UNxJI5L.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/7UNxJI5L.js" rel="modulepreload">
|
||||||
<link href="/dashboard/_app/immutable/chunks/Bzak7iHL.js" rel="modulepreload">
|
<link href="/dashboard/_app/immutable/chunks/Bzak7iHL.js" rel="modulepreload">
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
<div style="display: contents">
|
<div style="display: contents">
|
||||||
<script>
|
<script>
|
||||||
{
|
{
|
||||||
__sveltekit_9mpvth = {
|
__sveltekit_1mw0ef2 = {
|
||||||
base: "/dashboard",
|
base: "/dashboard",
|
||||||
assets: "/dashboard"
|
assets: "/dashboard"
|
||||||
};
|
};
|
||||||
|
|
@ -41,8 +41,8 @@
|
||||||
const element = document.currentScript.parentElement;
|
const element = document.currentScript.parentElement;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
import("/dashboard/_app/immutable/entry/start.BieeVrE-.js"),
|
import("/dashboard/_app/immutable/entry/start.C8fl2m83.js"),
|
||||||
import("/dashboard/_app/immutable/entry/app.hiopGwi-.js")
|
import("/dashboard/_app/immutable/entry/app.B1RqXwG0.js")
|
||||||
]).then(([kit, app]) => {
|
]).then(([kit, app]) => {
|
||||||
kit.start(app, element);
|
kit.start(app, element);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -19,13 +19,24 @@ const mockContext2D = {
|
||||||
clearRect: vi.fn(),
|
clearRect: vi.fn(),
|
||||||
fillRect: vi.fn(),
|
fillRect: vi.fn(),
|
||||||
fillText: vi.fn(),
|
fillText: vi.fn(),
|
||||||
|
strokeText: vi.fn(),
|
||||||
measureText: vi.fn(() => ({ width: 100 })),
|
measureText: vi.fn(() => ({ width: 100 })),
|
||||||
createRadialGradient: vi.fn(() => createMockGradient()),
|
createRadialGradient: vi.fn(() => createMockGradient()),
|
||||||
createLinearGradient: vi.fn(() => createMockGradient()),
|
createLinearGradient: vi.fn(() => createMockGradient()),
|
||||||
|
beginPath: vi.fn(),
|
||||||
|
closePath: vi.fn(),
|
||||||
|
moveTo: vi.fn(),
|
||||||
|
lineTo: vi.fn(),
|
||||||
|
quadraticCurveTo: vi.fn(),
|
||||||
|
arc: vi.fn(),
|
||||||
|
fill: vi.fn(),
|
||||||
|
stroke: vi.fn(),
|
||||||
font: '',
|
font: '',
|
||||||
textAlign: '',
|
textAlign: '',
|
||||||
textBaseline: '',
|
textBaseline: '',
|
||||||
fillStyle: '' as string | object,
|
fillStyle: '' as string | object,
|
||||||
|
strokeStyle: '' as string | object,
|
||||||
|
lineWidth: 1,
|
||||||
shadowColor: '',
|
shadowColor: '',
|
||||||
shadowBlur: 0,
|
shadowBlur: 0,
|
||||||
shadowOffsetX: 0,
|
shadowOffsetX: 0,
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ export class NodeManager {
|
||||||
|
|
||||||
// Text label sprite
|
// Text label sprite
|
||||||
const labelText = node.label || node.type;
|
const labelText = node.label || node.type;
|
||||||
const labelSprite = this.createTextSprite(labelText, '#e2e8f0');
|
const labelSprite = this.createTextSprite(labelText, '#94a3b8');
|
||||||
labelSprite.position.copy(pos);
|
labelSprite.position.copy(pos);
|
||||||
labelSprite.position.y += size * 2 + 1.5;
|
labelSprite.position.y += size * 2 + 1.5;
|
||||||
labelSprite.userData = { isLabel: true, nodeId: node.id, offset: size * 2 + 1.5 };
|
labelSprite.userData = { isLabel: true, nodeId: node.id, offset: size * 2 + 1.5 };
|
||||||
|
|
@ -339,6 +339,20 @@ export class NodeManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a label as a dark rounded "pill" with dim slate text.
|
||||||
|
///
|
||||||
|
/// The scene runs an UnrealBloomPass with threshold 0.2, so any bright
|
||||||
|
/// canvas pixels get smeared into a halo. Previously the labels were
|
||||||
|
/// near-white (#e2e8f0) text on a transparent background, which bloomed
|
||||||
|
/// into unreadable white blobs (issue filed by Sam 2026-04-19). The fix:
|
||||||
|
///
|
||||||
|
/// 1. A ~85%-opaque dark pill under the text so the background is
|
||||||
|
/// well below the bloom threshold, stopping the halo before it
|
||||||
|
/// spreads past the label bounds.
|
||||||
|
/// 2. Mid-luminance slate text (#94a3b8 by default) — still legible
|
||||||
|
/// but dim enough that bloom only adds a soft glow, not a blast.
|
||||||
|
/// 3. Smaller font (22px) and tighter sprite scale (9×1.2) so labels
|
||||||
|
/// don't visually compete with the node spheres they annotate.
|
||||||
private createTextSprite(text: string, color: string): THREE.Sprite {
|
private createTextSprite(text: string, color: string): THREE.Sprite {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
@ -352,15 +366,51 @@ export class NodeManager {
|
||||||
const label = text.length > 40 ? text.slice(0, 37) + '...' : text;
|
const label = text.length > 40 ? text.slice(0, 37) + '...' : text;
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.font = 'bold 28px -apple-system, BlinkMacSystemFont, sans-serif';
|
|
||||||
|
// Measure the label so the backing pill hugs the text instead of
|
||||||
|
// spanning the full canvas width (which would leave a giant empty
|
||||||
|
// dark bar on short labels like "fact" or "note").
|
||||||
|
ctx.font = '600 22px -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif';
|
||||||
|
const metrics = ctx.measureText(label);
|
||||||
|
const textWidth = metrics.width;
|
||||||
|
const padX = 14;
|
||||||
|
const padY = 9;
|
||||||
|
const pillW = Math.min(textWidth + padX * 2, canvas.width - 4);
|
||||||
|
const pillH = 40;
|
||||||
|
const pillX = (canvas.width - pillW) / 2;
|
||||||
|
const pillY = (canvas.height - pillH) / 2;
|
||||||
|
const radius = pillH / 2;
|
||||||
|
|
||||||
|
// Dark glass pill — low enough luminance that UnrealBloomPass at
|
||||||
|
// threshold 0.2 does not amplify its pixels.
|
||||||
|
ctx.fillStyle = 'rgba(10, 16, 28, 0.82)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(pillX + radius, pillY);
|
||||||
|
ctx.lineTo(pillX + pillW - radius, pillY);
|
||||||
|
ctx.quadraticCurveTo(pillX + pillW, pillY, pillX + pillW, pillY + radius);
|
||||||
|
ctx.lineTo(pillX + pillW, pillY + pillH - radius);
|
||||||
|
ctx.quadraticCurveTo(
|
||||||
|
pillX + pillW,
|
||||||
|
pillY + pillH,
|
||||||
|
pillX + pillW - radius,
|
||||||
|
pillY + pillH
|
||||||
|
);
|
||||||
|
ctx.lineTo(pillX + radius, pillY + pillH);
|
||||||
|
ctx.quadraticCurveTo(pillX, pillY + pillH, pillX, pillY + pillH - radius);
|
||||||
|
ctx.lineTo(pillX, pillY + radius);
|
||||||
|
ctx.quadraticCurveTo(pillX, pillY, pillX + radius, pillY);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Hairline stroke for definition at small camera distances.
|
||||||
|
ctx.strokeStyle = 'rgba(148, 163, 184, 0.18)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.8)';
|
|
||||||
ctx.shadowBlur = 6;
|
|
||||||
ctx.shadowOffsetX = 0;
|
|
||||||
ctx.shadowOffsetY = 2;
|
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.fillText(label, canvas.width / 2, canvas.height / 2);
|
ctx.fillText(label, canvas.width / 2, canvas.height / 2 + 1);
|
||||||
|
|
||||||
const texture = new THREE.CanvasTexture(canvas);
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
texture.needsUpdate = true;
|
texture.needsUpdate = true;
|
||||||
|
|
@ -374,7 +424,7 @@ export class NodeManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
const sprite = new THREE.Sprite(mat);
|
const sprite = new THREE.Sprite(mat);
|
||||||
sprite.scale.set(12, 1.5, 1);
|
sprite.scale.set(9, 1.2, 1);
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue