Please read all the sections below before writing a single line of code.
- JavaScript Style Guide
- Vue.js Style Guide
- CSS/SASS Style Guide
- Group Income Data Model Rules
- SBP Paradigm (Soon!)
While our build system should automatically detect most deviations from JS Standard, it might not catch all (especially in .vue
files).
If you notice any files not properly linted by standard
, this means there's a bug, and you're welcome to help fix it!
It is still on you to ensure your code conforms to the standard
spec, whether or not the linter catches everything.
Since this is a Vue.js project, any pull requests must follow Priority A rules mentioned in the Vue.js Style Guide, and should follow the Priority B rules. Please take the time to read at least those two sections.
For styling, we use Bulma.
- Everything that can be solved by using Bulma’s classes should be solved with them
- Theme overwrites that affect the whole application - for elements that could be found at the Bulma docs - should go to
/frontend/simple/sass
and be written in SCSS - Component specific styles should go to the component’s
<style>
tag, be written in SCSS, and be scoped to the component. Try to write as little of this as possible.
Bulma sometimes changes significantly. Therefore it's important:
- To access the correct documentation for the version of Bulma that we're using. (Check
package.json
, and then visit the version-specific docs, the earliest available of which is 0.4.4) - Never update Bulma "just because"! Always create an issue to update bulma first, and then create a single unique pull-request just for that issue. You will have to verify and fix any UI-differences that occur. Sometimes Bulma will remove or change CSS selector class names, etc. You must identify and fix all differences.
Group Income adds additional rules for how to write .vue
components.
These rules come out of many lessons learned. All new pull requests must follow these rules. The sections below first describe the Group Income data model, and then describe the actual rules in "How to organize data ()
".
Note: some old code unfortunately does not follow these rules, and we encourage contributors to help clean it up so that it does.
If you're familiar with Ethereum smart contracts, you'll be somewhat familiar with how Group Income structures its data, however, many of the terms you're familiar with will have slightly different meanings and implementations in Group Income.
For example, in Group Income, data is synchronized between users through "blockchains" and "smart contracts", but these chains and contracts have more in common with git
than they do with Ethereum.
Here are the primary differences:
- In Group Income, "blockchains" are smart contracts. Each contract is its own private chain of events called a contract chain.
- Each contract is identified by the hash of the transaction that created it, which is also the start of its contract chain.
- All contract chains created by Group Income are private and can only be accessed to by those authorized to. For example, a
GroupContract
can only be read and written to by current group members. - Data is synchronized between clients through a central node. Each group member has the ability to turn their client into this coordinating node, but every group uses a single always-online node to transmit and sync changes to logs. There is no consensus process beyond this node because no consensus process is needed since everyone already trusts each other.
Since every contract represents a log of events, you can run through the actions/events in the log to build up a state for the contract object represented by that event log.
In Group Income, the client runs chains through Vuex to build up the application state via the handleEvent()
function in frontend/simple/state.js
.
There is a direct 1-to-1 mapping between the contract event logs, and Vuex mutations and actions. The contracts are defined in shared/events.js
, and their Vuex mappings are defined in frontend/simple/js/events.js
.
There are two primary kinds of state in Group Income:
- Persistent state refers primarily to the
Vuex
state that gets serialized to disk, and in general it also refers to everything that creates that state. This includes the contracts and the transactions that create them. In other words, persistent state is the state that is at some point sent over the Internet, and it is the data that persists between application launches.- When the application starts, it loads data from disk (via
localStorage
) into the Vuex store. - The state for every contract we're subscribed to exists as a unique module in Vuex, the key for which is the hash of the first transaction that created the contract.
- Almost everything in the vuex state is serialized to disk to the local "database" (i.e.
localStorage
) so that it's available the next time the application starts. - Events for each contract we're subscribed to pass through the
handleEvent()
function instate.js
. Whenever we (the client) send out a transaction that modifies the group state, we first send it to the server, and only update the Vuex state when the server echoes the transaction back to us (and then it gets processed like all the others byhandleEvent()
).
- When the application starts, it loads data from disk (via
- Ephemeral state refers to everything else. Much of the data in Group Income does not need to be saved to disk or sent over the Internet. For example, none of the metadata related to validating forms needs to be saved to disk or kept around between application launches. And although some state might exist in contract chains (for example, the URL to a profile picture), that does not mean Group Income will persist every contract a user interacts with.
- The
latestContractState()
function instate.js
should be used in situations where data from a contract chain is needed momentarily, but might not need to be saved forever. It will fetch all of the events in a contract and use them to build up a final state for that contract. For example,latestContractState()
is used inJoin.vue
to show avatars for group members in a group the user is considering joining. Since the user might decide not to join a group, we do not subscribe to theIdentityContract
s of any of those users and do not persist any of that information (until the user actually joins the group). - All ephemeral state should be stored in a Vue component's
data ()
section. There is almost no exception to this.
- The
Having components keep track of their own state is considered poor practice. It makes the application difficult to reason about and maintain.
Therefore, components should be "purely functional" in the sense that virtually all of their state is derived from the vuex store, computed properties based on that store, or props that are passed in to the component from a parent component.
There are three exceptions, and all of them have to do with different types of ephemeral state (see above).
The object returned by data ()
should have only these keys:
form
— form validation metadata forvuelidate
.config
— some Vue.js components require bindings to config data (for example, thesliderConfig
for<vue-slider>
inTimeTravel.vue
andsteps
forcomponents/StepAssistant.js
inCreateGroup.vue
).ephemeral
— any data we might not care to save beyond the life of the view (unless some other action is performed) should be placed here. There shouldn't be much that fits in this category (as, for example, most error handling stuff should be handled byform
and in the<template>
section). However, it might be useful to cache some data temporarily (like a profile picture or URL) here.
Remember: if you can use a computed property based on the vuex store, it means you probably should.
Virtually everything in this project is going to be converted to SBP ("selector-based programming", see shared/sbp.js
).
Details about SBP will be written in a blog post soon. In the meantime, you are encouraged to adopt this paradigm wherever possible for your own code.
Search the project for sbp(
for examples, and speak with @taoeffect about it before diving in (at least until the docs for SBP are still waiting to be written).
SBP is the greatest thing to happen to programming since computers were invented! :D->-<