Commit Graph

85 Commits

Author SHA1 Message Date
EugeneTes
e1bd1709d7 fix(graph): always show edge filter/transform label
Drop the selected-only gate so the filter subject → transform destination
hint is visible on every edge, not just the focused one.
2026-08-27 19:52:29 +00:00
EugeneTes
c5d596a2a7 fix(graph): always render both handles on node cards
Round-2 spec dropped the input handle from source nodes and the output
handle from sink nodes so the topology's endpoints read as terminal at a
glance. But source/sink is *computed from graph topology* — a node with no
inbound edges is a "source". That meant every freshly-created (still-
unconnected) node classified as a source, lost its input handle, and could
never receive an edge — the graph became unusable the moment you tried to
build one from scratch.

Type is already carried by the SOURCE/ROUTER/SINK eyebrow + the top-border
weight on sources + the inverse fill on sinks. The handle presence was a
redundant fourth signal that broke basic functionality. Always render both;
keep the rest of the type language intact.
2026-08-27 19:26:18 +00:00
EugeneTes
85d5d183f0 feat(frontend): tesanti brand re-skin — palette, wordmark, graph, toast, confirm
Applies the tesanti brand pack (docs/brand/tesanti.md) across every admin
surface, replacing the old slate + orange Tailwind defaults with black + white
+ signal red, three variable-weight fonts (Space Grotesk / Inter / JetBrains
Mono), mono eyebrows above every heading, and a hairline-first (not shadow-
first) elevation language.

New primitives:
- Wordmark  — "VolcanicCore" in Space Grotesk 500 with the red signal dot.
- Toast     — provider + useToast() hook + 360px surface-2 card with slide+
              fade. Replaces every alert() (edge/node mutation failures).
- ConfirmDialog — provider + promise-based useConfirm() covering optional
              type-to-confirm gating. Replaces every confirm()/prompt()
              (user delete, node remove, edge delete). Zero browser dialogs
              left in the app.
- RouteEdge — custom React Flow edge that keeps the canvas quiet by default
              and surfaces the mono "filter → transform" label as an opaque
              pill only when the edge is selected.

Graph marquee:
- vc-node states (source / router / sink) differentiated by top-border
  weight, inverse fill for sinks, and eyebrow — never by color. Red is
  reserved for selected + error.
- Node width 240px; edge label overlap eliminated.
- Zero-node empty state with a red primary "Add node" CTA.

Screen coverage:
- Login          — centered wordmark + "NATS ROUTING CONTROL" eyebrow.
- AppShell       — 240px sidebar, numbered mono eyebrows, 2px red left-border
                   on the active item, Lucide log-out on sign-out.
- Users          — mono-headed table, outline-destructive delete, chips.
- Server         — surface-2 card headers, hairline dividers, outline OK/UP
                   status chips.
- Audit          — full mono table.
- Every error banner picked up the alert-triangle prefix and full accent
  border.

Icons: added lucide-react and standardized on it (X, AlertTriangle, Check,
Share2, LogOut). Every unicode × / control glyph swapped out.

Fonts self-hosted via @fontsource-variable/{space-grotesk,inter,
jetbrains-mono}; no external requests, CSP-friendly.
2026-08-27 17:51:12 +00:00
EugeneTes
00605dfc82 docs: CLAUDE.md — invariants surfaced by the NodeExample session
Three real bugs and one data-model quirk showed up while wiring the
Examples/NodeExample smoke test to production. Adding them as design
invariants so future sessions don't re-hit them:

- JS API subjects belong in publish.allow (request/reply direction)
- SIGHUP must go through libc.kill; procps isn't in the base image
- Node display name is not the JetStream stream name

Also add the Examples path to the repo layout, mention the end-to-end
smoke procedure, and gitignore .playwright-mcp/.
2026-08-27 16:06:57 +00:00
EugeneTes
1a5528dac6 fix(examples): NodeExample points at real stream name Test2
The node's display name is "Test_2" but its JetStream stream name (chosen
by the operator, constrained to [A-Za-z0-9_-]+) is "Test2". NATS API
calls target streamName, not the node's display name.
2026-08-27 16:05:27 +00:00
EugeneTes
b17af56d14 fix(config-writer): SIGHUP via libc kill(2) instead of /bin/kill
The aspnet:9.0 runtime base image doesn't ship procps, so
Process.Start(\"kill\", \"-HUP <pid>\") failed with ENOENT — the first
reconcile after boot wrote the new nats.conf to disk, then threw when
signalling, and subsequent reconciles saw the on-disk text already
matched the target and skipped the retry. Net effect: nats-server kept
running with whatever config it parsed at startup, and configuration
changes never took effect at runtime.

Switch to a direct P/Invoke to libc kill(2) so we don't depend on any
external binary.
2026-08-27 15:46:41 +00:00
EugeneTes
ddaa43d924 fix(config): JS API subjects belong in publish.allow, not subscribe.allow
The JetStream request/reply model has the CLIENT publishing requests to
$JS.API.<...> (and $JS.ACK.<...>) and receiving replies on _INBOX.>.
ConfigGenerator was granting these on the subscribe side instead, so
end users could never create consumers, fetch, ack, or query streams —
their JS calls silently timed out on unauthorized publishes. Also add
STREAM.INFO / CONSUMER.INFO / CONSUMER.DURABLE.CREATE / CONSUMER.DELETE,
which end-user clients need but weren't granted at all.

Tests now assert placement (publish vs subscribe block) so this can't
regress silently. The example switches to Test_2 as the destination
stream name to match the real deployment.
2026-08-27 15:37:56 +00:00
EugeneTes
ad1a2462e5 feat(examples): NodeExample — publish to Test, consume from Test 2
Small .NET 9 console app that opens two NATS connections, one per node,
to exercise the edge-routing feature end-to-end. Uses NATS.Client.Core
for the publisher and NATS.Client.JetStream for the ephemeral consumer.
2026-08-27 15:16:14 +00:00
VolcanicCore
4336eae7ca docs: CLAUDE.md — project context, deployment, invariants for future sessions 2026-08-27 15:07:38 +00:00
VolcanicCore
b874b9fb06 feat(nodes): allow empty subjects for pure receiver nodes
Removes the 'at least one subject required' validator from both the backend
POST /api/nodes endpoint and the frontend node drawer. NATS accepts streams
with no subjects — messages flow in via 'sources' (incoming edges) only.
Publishers can still be assigned but they'll get an empty publish allow list.
2026-08-27 15:01:41 +00:00
VolcanicCore
3cbde17c27 feat(frontend): editable node drawer (name/subjects/quotas) + danger-zone remove 2026-08-27 14:53:19 +00:00
VolcanicCore
9b1b5009bf feat(frontend): editable filter+transform in edge drawer with test-match preview 2026-08-27 14:45:42 +00:00
VolcanicCore
076249bd15 feat(layout): persist node positions in node_positions table
Adds a lightweight table + repository + POST /api/nodes/positions endpoint.
/api/graph now returns positions keyed by node id. Frontend uses saved
positions on load and debounce-saves 300ms after a drag ends.
2026-08-27 14:17:09 +00:00
VolcanicCore
5927636f9f feat(frontend): delete edges via drawer button or Delete/Backspace key 2026-08-27 14:12:29 +00:00
VolcanicCore
5df1fe85be fix(frontend): use useNodesState/useEdgesState so React Flow properly tracks measurements + connections 2026-08-27 14:08:19 +00:00
VolcanicCore
821f433ee8 fix(frontend): pre-set node width/height so React Flow doesn't hide them 2026-08-27 13:59:51 +00:00
VolcanicCore
baa6aafa66 fix(frontend): un-wrap NodeCard data so custom nodes render in v12 2026-08-27 13:55:27 +00:00
VolcanicCore
cddfb5d3ac fix(deploy): pull nats-server binary from official nats:alpine image
The previous stage downloaded from GitHub via curl, which fails under Coolify's
builder network with an opaque exit 2. Using the official image avoids the
network dependency entirely.
2026-08-27 13:37:47 +00:00
VolcanicCore
a0085cecb9 feat(deploy): docker-compose.yml with two-domain Traefik routing 2026-08-27 13:28:43 +00:00
VolcanicCore
f6328b17f3 test(e2e): admin creates two nodes via the UI 2026-08-27 13:19:55 +00:00
VolcanicCore
55adec643e test(e2e): Playwright login happy + bad-password paths 2026-08-27 13:17:39 +00:00
VolcanicCore
b4994eefdf feat(deploy): multi-stage Dockerfile (frontend + backend + nats-server) 2026-08-27 13:14:48 +00:00
VolcanicCore
71caaf467b feat(api): serve SPA from wwwroot with fallback for client routes 2026-08-27 13:08:27 +00:00
VolcanicCore
69f87e19ee feat(frontend): Server + Audit screens 2026-08-27 13:01:09 +00:00
VolcanicCore
4ce4143f44 feat(frontend): Users screen with inline assignments + copy-password 2026-08-27 13:00:05 +00:00
VolcanicCore
cd1a5b7bec feat(frontend): Graph screen with add-node dialog + read-only drawers 2026-08-27 12:58:56 +00:00
VolcanicCore
cfe7bbcdb2 feat(frontend): React Flow canvas with custom NodeCard + drag-connect 2026-08-27 12:56:55 +00:00
VolcanicCore
0d6748b61d feat(frontend): useGraph/useHealth mutations + admin WebSocket client 2026-08-27 12:55:00 +00:00
VolcanicCore
fe6ef5ab1f feat(frontend): Login screen with typed useLogin mutation 2026-08-27 12:53:24 +00:00
VolcanicCore
1e4f4d0465 feat(frontend): TanStack Router + Query + typed API client + placeholder screens 2026-08-27 12:52:10 +00:00
VolcanicCore
940acc7ae3 chore(frontend): Tailwind + shadcn utility deps + @/ path alias 2026-08-27 12:49:40 +00:00
VolcanicCore
d68e174a64 chore(frontend): Vite + React + TS scaffold with API proxy 2026-08-27 12:42:59 +00:00
VolcanicCore
0c9617ee20 docs: frontend+container implementation plan (Plan 3 of 3) 2026-08-27 12:30:17 +00:00
VolcanicCore
5d783f2a9e fix(reconciler): pass PORT_NATS_WS through to regenerated nats.conf 2026-08-27 12:22:40 +00:00
VolcanicCore
2fc3a5dd2c test(integration): edge removal stops flow; node removal preserves stream 2026-08-27 12:17:47 +00:00
VolcanicCore
10eeb44621 test(integration): diamond fan-in dedupes by Nats-Msg-Id 2026-08-27 12:15:54 +00:00
VolcanicCore
605f255b2b fix(nats): NATS 2.10 rejects FilterSubject + SubjectTransforms on one source 2026-08-27 12:14:02 +00:00
VolcanicCore
97a2760cb6 test(integration): edge sourcing actually moves messages A -> B 2026-08-27 12:13:16 +00:00
VolcanicCore
16f236bbfd test(integration): RealNatsFixture spins up nats-server per test + sanity 2026-08-27 12:09:30 +00:00
VolcanicCore
ab94edd536 fix(nats): reconciler auths as _reconciler in APP account
NATS forbids JetStream on the system account, so the admin client cannot
use SYS for stream ops. Add an internal _reconciler user to APP with full
JS + subject access (never surfaced in the UI), and change the admin
connection to auth as that user.

KICK-on-user-remove is deferred to a future two-connection design: for
now, ListCidsForUserAsync/KickCidAsync are no-ops. Removed users stay
connected until they voluntarily disconnect (SIGHUP alone doesn't
disconnect existing sessions).
2026-08-27 12:06:39 +00:00
VolcanicCore
b3e590ac0a feat(api): wire NATS supervisor, reconciler, traffic tap into Program.cs 2026-08-27 12:01:05 +00:00
VolcanicCore
1c08c143da feat(api): TrafficTap subscribes to _debug.node.> and broadcasts per-stream throughput 2026-08-27 11:57:06 +00:00
VolcanicCore
675cb258ae refactor(api): AdminBroadcaster parallel send + IAsyncDisposable 2026-08-27 11:55:58 +00:00
VolcanicCore
fecbcd8212 feat(api): /api/health reports live nats+reconciler status 2026-08-27 11:55:03 +00:00
VolcanicCore
d1f03b67ae refactor(api): /api/graph uses single-conn SnapshotBuilder 2026-08-27 11:54:19 +00:00
VolcanicCore
baa05f7c00 feat(api): fire reconciler trigger from mutations; SqliteErrorCode; txn edge check 2026-08-27 11:53:14 +00:00
VolcanicCore
f23a790de3 feat(api): Reconciler IHostedService — trigger-driven diff+apply loop 2026-08-27 11:49:48 +00:00
VolcanicCore
f7c4d5b36c feat(api): NatsSupervisor IHostedService — spawn/monitor/restart nats-server 2026-08-27 11:48:38 +00:00
VolcanicCore
88c4ab3198 feat(nats): INatsAdminClient with NATS.Net-backed impl for streams + $SYS RPCs 2026-08-27 11:47:21 +00:00
VolcanicCore
35c7f32c27 feat(nats): atomic ConfigWriter with SIGHUP + verify + last-good revert 2026-08-27 11:37:43 +00:00