Contributing¶
Thank you for your interest in participating in the project's development! Please read the Contributor Covenant Code of Conduct and the Contributor License Agreement first. If you plan to use LLMs to contribute, make sure you also read our AI and LLM policy.
The project is written in ClojureScript - a compiler for Clojure that targets JavaScript, and is based on re-frame - a framework for building Modern Web Apps in ClojureScript. You should probably take a look at their exceptional documentation first.
Style Guide¶
We try to follow the Clojure Style Guide as much as possible. You are also advised to read an additional resource about how to name Clojure functions by Stuart Sierra. In addition to the idiomatic names, we use the following conventions
e -> event
el, els -> element, elements
attr, attrs -> attribute, attributes
prop, props -> property, properties
w, h -> width, height
t -> time
h, m, s, ms -> hours, minutes, seconds, milliseconds
App structure¶
Main structure
src\
├── renderer\ -> Renderer Process
├── electron\ -> Main Process & Preload script
├── lang\ -> Translation files
└── worker\ -> Web Workers
We are trying to split our code under renderer into relatively independent modules, following re-frame's app structure suggestions with some additions.
module\
├── core.cljs -> entry point (will be evaluated on load)
├── db.cljs -> schema, validation
├── views.cljs -> reagent views
├── events.cljs -> event handlers
├── subs.cljs -> subscription handlers
├── handlers.cljs -> helper functions for db transformations
├── effects.cljs -> effect handlers
├── hierarchy.cljs -> multimethods and hierarchies
├── migrations.cljs -> data migrations
└── README.md -> documentation
Core ns¶
Core requires the rest of the namaspaces (subs, events, etc). We also register the corresponding actions and action groups.
Db ns¶
Db defines the malli schemas of the module, and the corresponding validators, explainers, and transformers.
Events db¶
Registers our re-frame events. Most events use functions from handlers to transform our db.
Subs db¶
Registers our re-frame subscriptions. This ns is usually very thin.
Handlers ns¶
Handlers contain pure functions that directly transform the db, or return a
value based on the db. Most functions under handlers take the db as their
first argument, so they can be easily composed using the thread-first macro ->.
Functions without input args usually return a transducer.
If none of the above is true, the function probably belongs to a different
namespace.
Effects ns¶
Registers all re-frame effects. Although we could use the events ns for this, we prefer isung dedicated ns to isolate all side effects and make stabing easier on tests.
Hierarchy ns¶
Hierarchy defines the required multimethods in order to allow extending the app
on the fly, based on a dispatch value. In a way, it is our plugin interface.
When the hierarchy ns is available for a module, the impl directory contains
the build-in defmethod implementations for the multimethods.
General re-frame recommendations¶
Avoid chaining events to create new ones. Always prefer composing transformation
functions. That is the whole purpose of handlers namespace.
Use interceptors sparingly. Although they look (and probably are) ingenious, it is hard to write and reason with them. Doing things explicitly, is usually easier to grasp and maintain.
Always use auto-qualified keywords (e.g. ::copy) for subscriptions, events and
effects. You can use as-alias to require those namespaces without evaluating
the registrations multiple times.
When you are not sure if you should add new state to the app db, the answer is usually yes, if the state is going to be used outside of the context of a single component view. If it also needs to be persisted to local storage, it should be serializable to json.
If you need to reuse the result of a subscription within an event, add reusable functions to handlers, and use them in subs and events. If it's an expensive calculation, you can write the result to the db within the event handler, or in an interceptor. Using the recently introduced flows is also an option, but we haven't tested that feature yet.
Spec¶
You can use the schema explorer to examine the app schemas and their relationships.
We use malli to describe the shape of our app db and selectively validate incoming data (e.g. file loading). We also use this spec to generate default values. Full db validation can be enabled on dev mode.
Function schemas
are selectively applied to pure and critical namespaces, such as utils and
handlers. By default, function schemas are instrumented only during tests to
avoid performance overhead. However, runtime instrumentation can also be enabled
in the development environment (see dev.cljs).
Useful development shortcuts¶
Ctrl+Shift+I -> Toggle devtools
Ctrl+Shift+X -> Toggle 10x
Ctrl+R -> Reload app