Nette
A research canvas for interconnected thoughts.
What was Nette?
-
Nette was a web-based research environment built around a spatial canvas and enriched blocks of interactive information, using ML systems for semantic analysis and generative AI to support content creation workflows.
-
Nette provided independent notetakers and researchers with a way to collect, organize, and navigate a living map of ideas, while supporting the sharing and cultivation of deep knowledge.
How I joined Nette?
-
I was drawn to Nette because it sat directly at the intersection of problems I cared deeply about: how tools shape the way people think, how ideas form before they become structured, and how software can support exploration without prematurely forcing conclusions.
-
At the time, I was especially interested in notetaking, spatial interfaces, second-brains, digital gardens, and research tools that treated ambiguity as a feature rather than a flaw. Nette offered a rare opportunity to engage with these ideas while working on a real product alongside a like-minded community.
-
I joined Nette as a software engineer focused on UI and UX, adopting the team’s core stack of Clojure, ClojureScript, and Datomic, while drawing on the broader JavaScript ecosystem to craft bespoke editors, custom layouts, reactive interfaces, and a 2D canvas built from web-native elements.
The Problem Space
-
Building a multi-media canvas required early decisions around scale, UI primitives, persistence, and data-first design.
- When I joined Nette, the system supported a large canvas of entities, with the entire workspace encoded in the EDN format and persisted as a workspace file.
- Using EDN as the underlying workspace format introduced an important trade-off: each workspace effectively captured a snapshot of a schema, meaning persisted workspaces could contain outdated schemas.
- To mitigate this trade-off, the workspace’s schema design closely followed Clojure’s principles around data modeling, relying on namespaced entities and attributes within nested maps to support schema evolution.
- As a benefit of the workspace essentially being a data structure, the client could be implemented as a declarative view of that state. This made rendering libraries like React, Reagent, and Re-Frame suitable choices for building the user interface and embedding editor frameworks such as TipTap.
-
With the foundations in place, state management and interop became primary concerns as the system evolved and integrated with the broader JavaScript ecosystem.
- By using ClojureScript on the client, it was natural to model application state as a collection of immutable data structures. This allowed the component system to take advantage of React optimizations, minimizing unnecessary re-renders and virtual DOM diffing through stable structural sharing.
- However, these immutable collections were specific to ClojureScript, and most third-party JavaScript libraries were not designed to consume them directly. This introduced friction at interop boundaries, where data often needed to be transformed between CLJS and JS representations.
- Without careful boundary management, it was easy to introduce performance bottlenecks by repeatedly encoding and decoding large data structures between the two layers. The client architecture therefore needed to be explicit about where interop occurred, converting data only when necessary and avoiding broad, eager transformations.
- Despite these costs, immutable data paid for itself in interaction-heavy workflows. For example, when rendering text documents, we could rely on the source data remaining unchanged while applying annotation layers on top. This made it possible to model document annotations as a collection of patches, which were stored and rendered independently against a stable source input.
-
When the core of the application was modeled with immutable data, we were able to choose rendering tools that naturally leveraged a data-first and declarative approach.
- The UI architecture was built on top of the ClojureScript libraries Reagent and Re-Frame. Together, these tools aligned well with Clojure’s immutable data model, allowing views to be expressed declaratively over client state using Reagent ratoms and Re-Frame subscriptions.
- Re-Frame subscriptions made it possible to derive reactive views of state through intermediate transformations, which worked well for many interaction patterns. However, we discovered that these transformations were not well suited for sharing or retaining their computed results across view lifecycles.
- For example, a subscription could select a document’s content and layer annotations on top, but the derived structure was only cached while the corresponding view was mounted. When the view was unmounted, the annotated representation was discarded.
- Re-annotating documents proved to be a costly operation, especially when users needed to switch seamlessly between original and annotated views. To address this, we introduced custom subscriptions that persisted intermediate computations as artifacts, allowing previously computed annotated documents to be reused rather than recomputed.
- This approach enabled lazy transformation when activating a view, while preserving the results for efficient reuse when the view was closed and reactivated.
-
Nette’s use of immutable data and reactive views of state was an opinionated architectural choice that required care and judgment when composing interactions within an event-driven model. Rather than becoming a constraint, this foundation proved to be an accelerant, enabling the development of meaningful interaction features that built directly on these principles.
Feature Showcase
Worm Selection
X-Ray Mode
Entity Graph
Learn More
- In 2025, Nette officially shutdown operations, so the original website may no longer be available.
- However, it’s still possible learn more about Nette by watching the various feature trailers on Youtube, or by following the creator of Nette.