the Dioxus commit (it’s broken but it exists)
okay so.
it does not work properly. I want to be upfront about that. it compiles, it launches, things appear on screen, but the interaction is janky, the canvas rendering fights with the DOM cards, and the layout worker doesn’t sync positions correctly yet. it’s a WIP commit and I’m committing it anyway because there’s genuinely a lot of infrastructure here and I don’t want to lose it.
three new crates
penumbra-theme: dark and light themes defined as Rust structs. colors, radii, glass blur config. to_css_vars() generates the full CSS custom properties string so the theme can be injected into the DOM at runtime. purple accent because penumbra means shadow and shadows are purple, obviously.
penumbra-canvas: a GraphCanvasRenderer trait with a WebCanvasRenderer that draws to an HTML canvas via web-sys. dot grid background, bezier curve edges between nodes, rounded-rect cards with titles. plus a NullCanvasRenderer for when there’s no canvas available. the RenderState struct holds the camera, nodes, edges, and selection state.
penumbra-thread: cross-platform threading. std::thread on native, wasm_thread on WASM. one #[cfg] block (the only one in the project, and it’s in a platform abstraction crate where it belongs). Worker struct with atomic cancellation flag. spawn_worker() and spawn_detached() helpers.
the app
Build/ui/penumbra-app/ is a full Dioxus desktop app. the component count looks scary but most of it is from the dioxus-components library (badge, button, card, dialog, dropdown menu, separator, sheet, sidebar, skeleton, tabs, tooltip). I styled them but didn’t write them. the custom ones are:
NoteCard: the little frosted-glass card that represents a note on the canvas. title, preview, positioned absolutely in world coordinates.
GraphCards: renders all notes as AnimatedCards inside a CSS-transformed container. each card gets a spring animation on mount via dioxus-motion so it scales in from zero. cards are draggable.
FloatingSidebar: the vertical icon bar on the left. grid, search, pin, tag, settings. just state toggles for now, no panels wired up yet.
TopBar: centered pill bar with the app name and search area.
Fab: “New note” button in the bottom right.
NoteEditor: full-screen editor view with title, body, and tags fields. auto-saves on back.
the bridge
bridge/mod.rs connects the UI to the backend. load_graph() and load_positions() pull from storage. restore_state() inserts everything into the graph and index. create_layout_engine() builds the GPU layout engine with all current nodes. start_layout_worker() spawns a background thread that steps the layout at 60fps, syncs node additions/removals, and publishes position updates through the event bus. sleeps longer when the layout has converged.
the interaction model
pan: mousedown on canvas starts tracking, mousemove applies delta to camera, mouseup stops.
zoom: wheel events on canvas adjust zoom level around the cursor position.
drag: mousedown on a note card captures the offset, mousemove in drag mode updates the note’s position directly.
note creation: click fab -> create empty note in graph -> wait for layout engine to assign position -> camera drifts to the new note (spring animation) -> switch to editor view. this is the flow from the ideas issue. it doesn’t work smoothly yet but the state machine is there.
what’s broken
everything, kind of. the canvas renderer and the DOM cards are two separate rendering paths that don’t coordinate well. the layout worker publishes positions but the signals don’t always pick them up in time. the camera drift animation triggers but sometimes snaps instead of drifting. the editor saves but doesn’t trigger re-embedding. the sidebar buttons toggle state but nothing happens.
it’s a prototype. sigh.
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.