Skip to content

Migration to v3 (typescript + python)#25

Open
vagkaratzas wants to merge 141 commits into
mainfrom
v3
Open

Migration to v3 (typescript + python)#25
vagkaratzas wants to merge 141 commits into
mainfrom
v3

Conversation

@vagkaratzas

Copy link
Copy Markdown
Collaborator

Full rewrite: migrated from R/Shiny to a FastAPI (Python) backend + Vite / TypeScript / Three.js frontend. Algorithms (layouts, clustering, topology) ported 1:1 via python-igraph.

Added

  • Satellite view — minimap overview of the scene (hidden layers excluded).
  • Command-pattern undo/redo — granular history for scene mutations.
  • Token-shared sessions — hand off a session to another app via ?session= URL (POST /api/external).
  • Reproducible node scatter (seeded), 60 FPS render loop as default, redesigned UI (side-drawer panels).

Changed

  • Zoom to cursor — mouse-wheel zoom anchors at the pointer instead of the scene center.

Fixed

  • Undraggable layers when zoomed in — orthographic drag ray rejected planes sitting behind its origin; layers froze at high zoom despite hover working.

vagkaratzas and others added 30 commits April 6, 2026 10:00
…init status check for FastAPI and frontend run dev connecting to it
Replace networkx/scipy/python-louvain with python-igraph (same C core as
R igraph — all v2 algorithms port 1:1, see SPEC). Package management via
uv: deps in pyproject.toml + uv.lock, requirements.txt deleted. Remove
stale algorithms/ scaffold dirs (SPEC now uses flat service modules).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…1 complete)

Dev: docker-compose with uv + node images, hot reload both sides,
VITE_PROXY_TARGET for in-network proxying. Prod: multi-stage build,
nginx serves Vite dist + proxies /api to uvicorn on one image (v2
r-base Dockerfile replaced; v2 preserved via v2-legacy tag).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports config/server_variables.R + global_variables.R into app/config.py
(RColorBrewer Set3/Set1 palettes inlined as hex). Endpoint serves the
v2 handler_initializeGlobals payload plus node palette, floor defaults,
topology metric and no-edge-layout lists. static_variables.R was mutable
Shiny state, ui_variables.R a footer year — both obsolete. All four R
config files deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the parseUploadedNetwork() chain from functions/input.R: subset
legit columns, trim, dedup (channel-aware), scale weights into 0.1..1
via mapper(), build Node_Layer keys. Validation mirrors
isNetworkFormatValid() (missing mandatory columns, non-numeric weight,
empty channel) as HTTP 400; MAX_LAYERS/MAX_EDGES enforced at the API
boundary. 12 pytest cases incl. real aspirin_3channels.tsv fixture.

input.R deletion deferred to Phase 7 (still holds JSON import, session
export, attribute uploads). Added pandas-stubs; mypy target 3.12 for
numpy stub syntax.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports functions/igraph/layout.R + general.R graph construction:
- services/graph.py: channel filter, perLayer/allLayers/nodesPerLayers
  scopes, simplify (sum parallel weights, keep loops)
- services/layouts.py: registry of all 11 v2 UI layouts as Graph.layout_*
  calls, per-request RNG seed (v2 set.seed(123)), returns 2D [y,z]
- no-edge layouts (Circle/Grid/Random) place isolated layer nodes as
  extra vertices (simpler equivalent of v2 filterPseudoNetwork)
24 pytest cases. layout.R deleted; general.R kept as spec through Phase 6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports functions/igraph/cluster.R. Clustering is an optional step of
POST /api/layout (not a separate endpoint): detect communities
(Louvain/Walktrap/Fast Greedy/Label Propagation via Graph.community_*),
lay super-nodes out with the global algorithm repelled from origin, lay
each community's members locally (real intra edges + tiny all-pairs
edges so disconnected members spread), translate into place. Response
gains a clusters map. 8 new pytest cases; cluster.R deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports functions/igraph/topology.R: Degree (raw Graph.degree, not
normalized centrality), Clustering Coefficient (weighted local
transitivity, isolates=0), Betweenness (edge-direction toggle +
weights). Values mapped per subgraph into [0.5, 2.5] via mapper()
(constant → 1); raw values returned for the View Data table. 7 pytest
cases. topology.R + general.R deleted — functions/igraph/ fully ported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- POST /api/session/import: ports parseUploadedJSON + isJSONValid
  (defaults for scene/layers/nodes/edges, channel drop, edge dedup,
  scramble flag). Loads real Arena3DwebApp_aspirin.json / export JSONs.
- POST /api/session/export: thin JSON download (stateless — frontend
  owns the state).
- POST /api/external + GET /api/external/{token}: session stored under
  a random token in tmp/ with 24h TTL sweep + path-traversal guard;
  resolves back through the same normalizer (SPEC §5).
- VR (functions/vr.R) DROPPED, not ported — depended on external
  bib.fleming.gr hosting + A-Frame, out of scope (see MIGRATION.md).

11 pytest cases. Deleted init.R, general.R, reset.R, render.R,
js_handling.R, vr.R. input.R kept: attribute-upload functions still
to port to a future /api/attributes endpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Foundation singletons:
- bus/index.ts: typed pub/sub, unsubscribe handle (replaces rshiny bridge)
- store/index.ts: single AppState source of truth
- commands/base.ts: Command + CommandHistory (undo/redo, emits
  history:changed)
- api/client.ts: hand-written typed fetch wrappers mirroring backend
  Pydantic models (no codegen dependency) — network/layout/topology/
  session/external

5 Vitest tests; tsc + eslint clean (skipLibCheck for third-party stubs).
Verified live: config/network/layout return 200, OpenAPI exposes all 9
paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migrate Scene/Layer/Node/Edge from www/js/classes/ to typed
frontend/src/three/ on npm three r170.

Key finding: vendored www/js/three/three.js was byte-identical to
stock Three.js r117 (diff = only a missing trailing newline). SPEC's
"manually patched copy" claim is wrong — no core patches exist, so no
src/three/ subclasses were needed.

- constants.ts: static geometry + 280-color palette (verbatim from v2)
- runtime.ts: typed ctx singleton replacing v2 ambient globals
  (registries, screen bounds, edge tunables, color-priority flags).
  nodeGroups/layerGroups were new Map() but only bracket-accessed in
  v2 -> typed as plain records.
- Scene/Layer/Node/Edge.ts: faithful ports; only API change is
  THREE.Math -> THREE.MathUtils.
- three.test.ts: 16 Vitest tests across all four classes.

Deferred (ponytail-marked): Scene.rotate() DOM slider + Shiny glue and
Layer.getColor() picker DOM read -> Phase 11 canvas_controls.

Delete www/js/classes/ + three.js. Keep matrix4.js/drag_controls.js
as living spec until Phase 11.

tsc + eslint + prettier clean; 21/21 vitest pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the 8 Command classes in src/commands/scene.ts, each
capturing before/after state on the Phase 9 Three.js object model so
CommandHistory can undo/redo:

- ChangeNodeColorCommand / ChangeNodeSizeCommand
- ChangeEdgeColorCommand (captures colors + edgeFileColorPriority)
- MoveLayerCommand (position/rotation/scale; redraws inter-layer edges)
- ApplyLayoutCommand (positions + optional cluster IDs/colors, one step)
- ApplyTopologyCommand (node scales)
- ChangeThemeCommand (store-only via injected getter/setter)
- LoadNetworkCommand (build closure + ctx registry snapshot for undo)

Commands are thin: they apply/reverse already-decided values. API
fetch and position/scale math is the Phase 11 actions' job, which will
construct these commands with the results.

Supporting changes:
- runtime.ts: add ctx.edgeObjects registry + snapshotRegistries /
  restoreRegistries helpers (RegistrySnapshot).
- bus: add layer:moved event.

9 Vitest tests (execute/undo/redo per command). tsc + eslint clean;
30/30 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the portable, unit-testable slice of the v2 object_actions:
src/actions/layout.ts — executeLayout coordinate normalization plus
applyLayout / applyTopology, which fetch from the API and dispatch the
Phase 10 ApplyLayoutCommand / ApplyTopologyCommand (one undo step each).

Confirmed against backend: /api/layout returns raw igraph [y,z] coords
(no layer-width normalization), so the frontend still performs the v2
mapping into [-minWidth/2, minWidth/2] * layer scale. Topology scales
are already mapped server-side and applied verbatim.

Re-scope the rest of Phase 11: the remaining 9 action files are coupled
to the renderer, camera, animate() loop, raycaster, DragControls, CSS2D
labels, and the UI panels — all built in Phases 12-13. They move to
those phases (documented in PLAN.md) rather than being stubbed now, and
www/js/object_actions/ deletion moves with them.

ponytail: allLayers scope only; perLayer/local layout refinements
deferred to the Phase 13 scope UI.

5 Vitest tests (normalize + applyLayout + applyTopology). tsc + eslint
clean; 35/35 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stand up the render spine so the app draws its first pixels. Verified
live with playwright-cli: /api/config loads, the WebGL canvas mounts,
and the tilted coordinate axes render.

- src/actions/screen.ts: port of screen.js — renderer, camera
  (orthographic), window bounds, raycaster, FPS-limited animate() loop.
  Shiny sync calls dropped. (screen.js was deferred out of Phase 11 for
  exactly this dependency.)
- src/main.ts: port of on_page_load.js — config fetch -> renderer/
  camera/scene setup -> mount canvas into #app -> animate. Exposes
  window.__arena = { ctx, history } so Playwright can read 3D scene
  state via page.evaluate (WebGL is opaque to the a11y tree).
- src/event_listeners.ts: window resize + Ctrl+Z / Ctrl+Shift+Z /
  Ctrl+Y undo/redo wired to CommandHistory.
- src/utils.ts: port of general.js (pure helpers), 6 Vitest tests.
- runtime.ts: add ctx.renderer/camera/fps + screen bounds (persist
  across network reloads; not reset by resetContext).

Deleted www/js/general.js (fully ported + tested).

Deferred: the canvas mouse/keyboard scene controls (clickDown/Drag/Up,
sceneZoom, keyPressed, right-click) land with canvas_controls (still in
the Phase 11 list). Documented in PLAN.md.

Updated PLAN.md Phase 13 to drive/verify DOM panels with playwright-cli
(@playwright/cli, installed) against the window.__arena hook.

tsc + eslint clean; 41/41 vitest pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…artial)

buildNetwork consumes backend-parsed NetworkData: fresh Scene + ctx
registries, layers spread on x, nodes scrambled per layer, edges grouped
per src---trg pair with channel colors. loadNetwork wraps it in
LoadNetworkCommand so an upload is one undo step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of www/js/object_actions/node.js pure parts: raycaster hover,
double-click/search/select-all selection synced to the store, repaintNode
(cluster branch folded into Node.getColor), label-flag priorities using
Layer.isVisible instead of DOM checkboxes, shape/color-priority setters.
scrambleNodes moves here from network.ts. ctx gains lastHoveredNodeIndex,
selectedNodeColorFlag and the two node-label flags.

DOM-bound pieces (search bar, sliders, lasso, node description div,
attribute upload) stay with canvas_controls.ts / Phase 13 UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 12 partial)

Port of www/js/object_actions/edge.js pure parts: renderInterLayerEdges
now runs every animate() tick (remove while dragging/paused/invisible,
v2 locked-flag redraw pair), channel color/visibility, unselectAllEdges,
and the edge-setting setters (direction, arrow sizes, width-by-weight,
opacities, color priorities, curvatures). redrawIntra/InterLayerEdges
move here from commands/scene.ts private helpers. ctx gains the four
inter-layer render flags.

Channel UI lists/picker stay with Phase 13; edge attributes upload with
POST /api/attributes; edge build already lives in network.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…12 partial)

Port of www/js/object_actions/layer.js pure parts: initialSpreadLayers
moves here from network.ts, raycaster hover with red highlight + repaint,
double-click/selectLayer/select-all selection synced to the store,
repaintLayers with default/picker color priority, per-layer visibility
and node-label toggles that recompute node label flags, plus the
coords/opacity/wireframe/color-priority setters. ctx gains
lastHoveredLayerIndex, hoveredLayerPaintedFlag, renderLayerLabelsFlag.

Checkbox group + color-picker DOM stay with Phase 13; DragControls and
the held-key/slider rotate/move/scale intervals with canvas_controls.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of www/js/object_actions/themes.js: THEMES presets (light/dark/gray
from the v2 buttons), applyTheme sets renderer clear color,
ctx.edgeDefaultColor/labelColor, reassigns channel colors from the config
palette and repaints layers picker-priority with the theme floor color.
registerThemeListener() subscribes to theme:changed in main.ts, so
ChangeThemeCommand execute/undo now performs the concrete recolour.

Supporting: ctx.labelColor (v2 globalLabelColor), repaintLayers gains an
optional pickerColor override, assignChannelColorsFromPalette lands in
edge.ts. Theme buttons DOM + channel edit list stay with Phase 13; label
recolour with labels.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of www/js/object_actions/labels.js keeping v2's plain
absolutely-positioned divs in #labelDiv (no CSS2DRenderer dep; inline
position styling until the Phase 13 stylesheet). createLabels runs on
network build; flag-gated renderLayerLabels/renderNodeLabels register as
animate hooks via a new registerAnimateHook in screen.ts, which avoids a
screen->labels->node->screen import cycle. showLayer/NodeLabels modes,
label resize and setLabelColor ported; themes.ts now recolors labels on
theme change and decideNodeLabelFlags raises the node render flag as v2
did. ctx gains renderNodeLabelsFlag + the two layer-label flags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of the canvas half of www/js/event_listeners.js plus the held-key
and lasso helpers it pulled from node.js/layer.js: wheel zoom, z/x/c
axis keys + arrow-key pan (modern event.key), clickDown/Drag/Up with
left-drag pan, middle-drag orbit, held-key node-translate/layer-rotate,
shift-lasso rectangle select (dim to 0.5, select on mouse-up), and
dblClick with v2's node -> layer -> unselect-all priority.
registerCanvasControls() wires the listeners onto the mounted canvas
from main.ts (tabIndex for keydown, scroll/autoscroll prevention,
mouseleave button release). Shiny syncs and 100ms debounces dropped.

Right-click node menu stays with right_click_menu.ts; DragControls layer
dragging and the nav-button/slider table with Phase 13.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…artial)

Port of www/js/object_actions/right_click_menu.js plus the context-menu
builder from event_listeners.js: selectNeighbors, selectMultiLayerPath
(v2 starting/current-layer exclusions), recursive selectDownstreamPath
over inter-layer edges, and the executeCommand tail (render flags, label
flags, store sync). The <select> menu is placed over the clicked node in
#labelDiv; Link opens the node URL. Wired to the canvas contextmenu
event and removed on clickUp in canvas_controls, as v2 did.

Loader spinner + description panel content stay with Phase 13 UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x4.js

Hand-port of v2's tweaked three.js DragControls copy onto npm three math
(the matrix4.js shim goes away). v2 behavior kept: drag engages only
while the left button is held over a hovered layer plane, label render
flags raised while dragging, camera-facing drag plane with parent-space
offset. On release it now raises the inter-layer edge render flag and
emits layer:moved (v2 routed this through Shiny syncs). The planes list
is read live from ctx so a single registration survives network reloads
(v2 recreated DragControls per load). Registered from main.ts.

Both v2 living-spec files deleted; MIGRATION.md rows ticked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ject_actions)

screen.js, labels.js, themes.js, right_click_menu.js,
event_listeners.js and on_page_load.js are fully covered by the ported
TypeScript actions and main.ts; MIGRATION.md rows ticked.

Kept as living spec for their unported remainders: layout.js
(predefined/star/cube layouts), network.js (importNetwork session JSON),
node.js/edge.js/layer.js/canvas_controls.js (Phase 13 DOM), config/,
rshiny_handlers.js/rshiny_update.js (map to Phase 13 panels).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
buildFromSession consumes the backend-normalized SessionData (now typed
in api/client.ts): scene position/scale/rotation + renderer clear color,
full layer transforms (initial spread only when the backend
generate_coordinates flag says the JSON carried none), nodes with
positions/scale/color/url/descr (scramble per flag within min layer
width), edges with per-row file colors and opacity as the weight, and
the v2 setJSONExtras flags (label color, direction, width-by-weight,
edgeFileColorPriority). loadSession wraps it in LoadNetworkCommand as
one undo step. createEdgeObjects gains the v2 decideEdgeColors rule: a
row's own color wins over the channel palette. The store receives a
NetworkData equivalent so later layout/topology calls work on imported
sessions too.

network.js is now fully ported and deleted; MIGRATION.md ticked.
Session export + File-panel wiring land with Phase 13.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vagkaratzas and others added 27 commits July 4, 2026 16:23
Ctrl+Z with the canvas focused armed axis-transform mode; a following
drag moved nodes/layers unintentionally. Skip axis arming on modified
keydowns.

Closes L3 in PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The module-local currentTheme shadowed store.currentTheme, which was
never written. Route the ChangeThemeCommand getter/setter through the
store so there is one source of truth.

Closes L4 in PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An edge channel missing from ctx.channelColors produced
new THREE.Color(undefined) -> silent white. Fall back to the edge
default color.

Closes L5 in PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading it at import time froze the value; env changes (redeploy,
tests) were ignored. Read it inside the handler.

Closes L6 in PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The native option popup rendered with the OS default light background
while inheriting light --arena-text, leaving layout/clustering/topology
select options light-on-light. Set an opaque dark background on
.form-select option/optgroup.

Closes U1 in PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
uv audit: no known vulnerabilities. 62 backend tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Majors: three 0.170→0.185, typescript 5→6, eslint 9→10, vite 6→8,
vitest 2→4, typescript-eslint 8.58→8.62, plus playwright/prettier/@types.

Fallout fixed:
- add src/vite-env.d.ts (TS6 now errors on untyped CSS side-effect imports)
- drop redundant type assertions flagged by newer typescript-eslint/@types/three

107 unit tests pass, tsc + eslint clean, build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
raycasting undraggable layer bug finally fixed!

@fbaltoumas fbaltoumas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve, let's switch to this and keep working on it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants