refactor(ts): make port effect native

This commit is contained in:
elpresidank 2026-06-06 10:33:10 -05:00
parent 2868ced2d3
commit b6759e75df
113 changed files with 4140 additions and 4554 deletions

View file

@ -10,18 +10,18 @@
"qa:browser": "playwright test"
},
"dependencies": {
"@effect/ai-anthropic": "4.0.0-beta.75",
"@effect/ai-openai": "4.0.0-beta.75",
"@effect/ai-openrouter": "4.0.0-beta.75",
"@effect/atom-react": "4.0.0-beta.75",
"@effect/openapi-generator": "4.0.0-beta.75",
"@effect/opentelemetry": "4.0.0-beta.75",
"@effect/platform-browser": "4.0.0-beta.75",
"@effect/platform-bun": "4.0.0-beta.75",
"@effect/platform-node": "4.0.0-beta.75",
"@effect/platform-node-shared": "4.0.0-beta.75",
"@effect/tsgo": "0.13.0",
"@effect/vitest": "4.0.0-beta.75",
"@effect/ai-anthropic": "4.0.0-beta.78",
"@effect/ai-openai": "4.0.0-beta.78",
"@effect/ai-openrouter": "4.0.0-beta.78",
"@effect/atom-react": "4.0.0-beta.78",
"@effect/openapi-generator": "4.0.0-beta.78",
"@effect/opentelemetry": "4.0.0-beta.78",
"@effect/platform-browser": "4.0.0-beta.78",
"@effect/platform-bun": "4.0.0-beta.78",
"@effect/platform-node": "4.0.0-beta.78",
"@effect/platform-node-shared": "4.0.0-beta.78",
"@effect/tsgo": "0.14.0",
"@effect/vitest": "4.0.0-beta.78",
"@tanstack/react-query": "^5.75.0",
"@trustgraph/client": "workspace:*",
"clsx": "^2.1.0",
@ -36,7 +36,7 @@
"zustand": "^5.0.0"
},
"devDependencies": {
"@effect/vitest": "4.0.0-beta.75",
"@effect/vitest": "4.0.0-beta.78",
"@playwright/test": "^1.57.0",
"@tailwindcss/vite": "^4.1.0",
"@types/react": "^19.1.0",

View file

@ -18,7 +18,7 @@ export default defineConfig({
video: "retain-on-failure",
},
webServer: {
command: `bun run dev -- --host 127.0.0.1 --port ${port} --strictPort`,
command: `WORKBENCH_QA=1 bun run dev -- --host 127.0.0.1 --port ${port} --strictPort`,
cwd: ".",
url: baseURL,
reuseExistingServer: false,

View file

@ -353,12 +353,12 @@ const ChatMessageSchema = S.Struct({
id: S.String,
role: S.Union([S.Literal("user"), S.Literal("assistant"), S.Literal("system")]),
content: S.String,
timestamp: S.Number,
timestamp: S.Finite,
isStreaming: S.optionalKey(S.Boolean),
metadata: S.optionalKey(S.Struct({
model: S.optionalKey(S.String),
inTokens: S.optionalKey(S.Number),
outTokens: S.optionalKey(S.Number),
inTokens: S.optionalKey(S.Finite),
outTokens: S.optionalKey(S.Finite),
})),
agentPhases: S.optionalKey(S.Struct({
think: S.String,

View file

@ -14,6 +14,8 @@ import {
localName,
type GraphNode,
type GraphLink,
directedGraphLinkProps,
DEFAULT_GRAPH_NODE_COLOR,
} from "@/lib/graph-utils";
import type { ExplainEvent } from "@trustgraph/client";
import type { ForceGraphProps } from "react-force-graph-2d";
@ -34,7 +36,7 @@ function paintNode(node: GraphNode, ctx: CanvasRenderingContext2D, globalScale:
const y = node.y ?? 0;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = node.color ?? "#5b80ff";
ctx.fillStyle = node.color ?? DEFAULT_GRAPH_NODE_COLOR;
ctx.fill();
const fontSize = Math.max(9 / globalScale, 1.5);
ctx.font = `${fontSize}px Inter, sans-serif`;
@ -115,7 +117,7 @@ export function ExplainGraph({ explainEvents, collection }: ExplainGraphProps) {
nodeCanvasObject={paintNode}
linkCanvasObjectMode={() => "after"}
linkCanvasObject={paintLink}
linkColor={() => "rgba(120,120,140,0.32)"}
{...directedGraphLinkProps}
/>
</Suspense>
</div>

View file

@ -1,24 +1,34 @@
/**
* Ambient glow background forest green radial blobs that drift and pulse.
* Ambient glow background -- forest green radial fields that drift and pulse.
*
* Ported from beep-effect4's GlowEffectPaper, adapted for plain CSS
* with multiple independent blobs for organic movement.
* with multiple independent fields for organic movement.
*/
const primaryGlow = "radial-gradient(ellipse at center, var(--tg-glow-primary-start) 0%, var(--tg-glow-primary-mid) 40%, transparent 70%)";
const secondaryGlow = "radial-gradient(ellipse at center, var(--tg-glow-secondary-start) 0%, var(--tg-glow-secondary-mid) 45%, transparent 70%)";
const tertiaryGlow = "radial-gradient(ellipse at center, var(--tg-glow-tertiary-start) 0%, var(--tg-glow-tertiary-mid) 50%, transparent 70%)";
export function GlowBackground() {
return (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 z-0 overflow-hidden animate-[glow-fade-in_1.2s_ease-out_forwards] opacity-0"
>
{/* Primary blob — large, centered, slow drift */}
<div className="absolute left-1/2 top-1/3 h-[70vh] w-[70vw] -translate-x-1/2 -translate-y-1/2 animate-[glow-drift-1_20s_ease-in-out_infinite] rounded-full bg-[radial-gradient(ellipse_at_center,rgba(61,125,61,0.35)_0%,rgba(45,99,45,0.15)_40%,transparent_70%)] blur-[80px]" />
<div
className="absolute left-1/2 top-1/3 h-[70vh] w-[70vw] -translate-x-1/2 -translate-y-1/2 animate-[glow-drift-1_20s_ease-in-out_infinite] rounded-full blur-[80px]"
style={{ background: primaryGlow }}
/>
{/* Secondary blob — smaller, offset right, faster */}
<div className="absolute right-[10%] top-[20%] h-[50vh] w-[40vw] animate-[glow-drift-2_15s_ease-in-out_infinite] rounded-full bg-[radial-gradient(ellipse_at_center,rgba(92,154,92,0.28)_0%,rgba(61,125,61,0.12)_45%,transparent_70%)] blur-[60px]" />
<div
className="absolute right-[10%] top-[20%] h-[50vh] w-[40vw] animate-[glow-drift-2_15s_ease-in-out_infinite] rounded-full blur-[60px]"
style={{ background: secondaryGlow }}
/>
{/* Tertiary blob — bottom left, subtle */}
<div className="absolute bottom-[5%] left-[15%] h-[45vh] w-[45vw] animate-[glow-drift-3_25s_ease-in-out_infinite] rounded-full bg-[radial-gradient(ellipse_at_center,rgba(33,78,33,0.30)_0%,rgba(26,58,26,0.12)_50%,transparent_70%)] blur-[70px]" />
<div
className="absolute bottom-[5%] left-[15%] h-[45vh] w-[45vw] animate-[glow-drift-3_25s_ease-in-out_infinite] rounded-full blur-[70px]"
style={{ background: tertiaryGlow }}
/>
</div>
);
}

View file

@ -51,6 +51,15 @@
--font-mono: "JetBrains Mono", ui-monospace, monospace;
}
:root {
--tg-glow-primary-start: rgba(61, 125, 61, 0.35);
--tg-glow-primary-mid: rgba(45, 99, 45, 0.15);
--tg-glow-secondary-start: rgba(92, 154, 92, 0.28);
--tg-glow-secondary-mid: rgba(61, 125, 61, 0.12);
--tg-glow-tertiary-start: rgba(33, 78, 33, 0.30);
--tg-glow-tertiary-mid: rgba(26, 58, 26, 0.12);
}
/* Base layer: dark background, light text by default */
@layer base {
*,
@ -182,4 +191,11 @@ html.light {
--color-success: #16a34a;
--color-warning: #854d0e;
--color-error: #b91c1c;
--tg-glow-primary-start: rgba(61, 125, 61, 0.28);
--tg-glow-primary-mid: rgba(92, 154, 92, 0.16);
--tg-glow-secondary-start: rgba(45, 99, 45, 0.22);
--tg-glow-secondary-mid: rgba(61, 125, 61, 0.12);
--tg-glow-tertiary-start: rgba(33, 78, 33, 0.18);
--tg-glow-tertiary-mid: rgba(92, 154, 92, 0.10);
}

View file

@ -1,6 +1,6 @@
import type { Triple, Term } from "@trustgraph/client";
import { Match } from "effect";
import type { NodeObject, LinkObject } from "react-force-graph-2d";
import type { ForceGraphProps, NodeObject, LinkObject } from "react-force-graph-2d";
// ---------------------------------------------------------------------------
// Constants
@ -32,6 +32,32 @@ export interface GraphData {
links: GraphLink[];
}
export const DEFAULT_GRAPH_NODE_COLOR = "#82b582";
const GRAPH_NODE_PALETTE = [
DEFAULT_GRAPH_NODE_COLOR,
"#5c9a5c",
"#3d7d3d",
"#aed1ae",
"#22c55e",
"#eab308",
"#a1a1aa",
"#71717a",
];
export const directedGraphLinkProps = {
autoPauseRedraw: false,
linkColor: "rgba(161,161,170,0.55)",
linkWidth: 1.4,
linkDirectionalArrowLength: 9,
linkDirectionalArrowRelPos: 0.58,
linkDirectionalArrowColor: "rgba(174,209,174,0.98)",
linkDirectionalParticles: 1,
linkDirectionalParticleSpeed: 0.005,
linkDirectionalParticleWidth: 2.2,
linkDirectionalParticleColor: "rgba(92,154,92,0.95)",
} satisfies Partial<ForceGraphProps<GraphNode, GraphLink>>;
// ---------------------------------------------------------------------------
// Term helpers
// ---------------------------------------------------------------------------
@ -66,8 +92,8 @@ export function hashColor(s: string): string {
for (let i = 0; i < s.length; i++) {
hash = s.charCodeAt(i) + ((hash << 5) - hash);
}
const hue = ((hash % 360) + 360) % 360;
return `hsl(${hue}, 60%, 55%)`;
const index = Math.abs(hash) % GRAPH_NODE_PALETTE.length;
return GRAPH_NODE_PALETTE[index];
}
// ---------------------------------------------------------------------------
@ -103,7 +129,7 @@ export function triplesToGraph(triples: Triple[]): {
nodeMap.set(uri, {
id: uri,
label: labelMap.get(uri) ?? localName(uri),
color: type !== undefined ? hashColor(localName(type)) : "#5b80ff",
color: hashColor(type !== undefined ? localName(type) : uri),
degree: 0,
});
}

View file

@ -27,6 +27,8 @@ import {
termValue,
type GraphNode,
type GraphLink,
directedGraphLinkProps,
DEFAULT_GRAPH_NODE_COLOR,
} from "@/lib/graph-utils";
import type { ForceGraphProps } from "react-force-graph-2d";
import { Badge } from "@/components/ui/badge";
@ -120,7 +122,7 @@ function paintNode(showLabels: boolean) {
const y = node.y ?? 0;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = node.color ?? "#5b80ff";
ctx.fillStyle = node.color ?? DEFAULT_GRAPH_NODE_COLOR;
ctx.fill();
if (!showLabels || globalScale < 0.7) return;
const fontSize = Math.max(10 / globalScale, 2);
@ -257,7 +259,7 @@ export default function GraphPage() {
nodeCanvasObject={paintNode(view.showLabels)}
linkCanvasObjectMode={() => "after"}
linkCanvasObject={paintLink}
linkColor={() => "rgba(120,120,140,0.32)"}
{...directedGraphLinkProps}
nodePointerAreaPaint={(node, color, ctx) => {
ctx.fillStyle = color;
ctx.beginPath();

View file

@ -3,8 +3,23 @@ import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
const isWorkbenchQa = process.env.WORKBENCH_QA === "1";
export default defineConfig({
plugins: [react(), tailwindcss()],
plugins: [
react(),
tailwindcss(),
{
name: "trustgraph-workbench-qa-otel",
configureServer(server) {
if (!isWorkbenchQa) return;
server.middlewares.use("/otel", (_request, response) => {
response.statusCode = 204;
response.end();
});
},
},
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),