Fifthtry

Tutorial: ToDo App

While doing these, update this page with instructions so others can follow it along.

realm-starter.sh

Using realm-starter.sh to spin up the barebones Realm app.

sh <(curl -L https://bit.ly/3hCRa8v)

TODO: move realm-starter.sh to realm-start repo, and use that URL instead of bitly.

Browse to the directory which you specified in the input by using cd.

Enter the nix shell using nix-shell --pure --run zsh. Check whether everything works correctly by running the command cargo run -- --test and open the browser to view http://127.0.0.1:3000.

TODO: create a file named pure, which can be invoked as ./pure which will contain nix-shell --pure --run zsh line.

#!/bin/sh

echo "What do you want to name the project as?"
read ProjName

echo "Downloading zip package from GitHub\n"
curl -L https://github.com/amitu/realm-starter/archive/master.zip -o master.zip
echo "Finished Downloading\n"

unzip master.zip >> /dev/null

mv realm-starter-master/ ${ProjName}/

# Clean Up
rm master.zip

cd ${ProjName}

git init

cd ..

echo "\nYour project is ready. Type 'cd ${ProjName}' to enter the directory and start working\n"

Configuring your IDE

The IDE we have find best for working with realm project is CLion by JetBrains.

  1. Ensure Elm plugin is installed.
  2. Open Settings/Preferences (Linux: Ctrl+Alt+S, Mac: Cmd-Comma).
  3. Go to Languages & Frameworks -> Elm.
  4. Elm Compiler: Location: Do which elm from inside nix-shell to get path to elm binary and paste here.
  5. elm-format: Location: Do which elm-formatfrom inside nix-shell to get path ofelm-format` binary and paste the path here.
  6. elm-format: Run when file saved?: Check this checkbox.
  7. In Editor -> Inspections, under Elm, select “Unused Imports” and set its severity to “Error”.

TODO: elm.json

TODO: Rust configuration.

Raw Tutorial

Functionality we need:

  1. Show a list of ToDos
  2. Click toogle between the finished/unfinished state of the individual ToDo
  3. On hover show the cross button which allows us to delete a ToDo

We start by modelling how we want to store data and thus define the data structures we would be using/passing around. Thus, let’s start with small pieces and build on them.

First one is the Item which is a record to store the details of a single ToDo item.

type alias Item =
    { title : String
    , done : Bool
    }

The we want to pass a list of Item (or ToDos) to the whole page so that it can render a list of ToDos, each with their own property. So we define Config.

type alias Config =
    { list : List Item
    }

Then we want to add feature that it will allow us to cancel a ToDo by showing a cross when we hover over it. So we need a place to store the information which can help us in achieving that goal. For this we choose to define Model.

type alias Model =
    { list : List Item
    , hover : Maybe Int
    }

To transmit the information stored in the data structures, we choose to use JSON. Thus, we write the JSON decoder first which will take a JSON value and convert it in a data structure that we parse and use in Elm.

Let’s start by writing the decoder for Item. This JSON contains two fields title which is a String and done which is a boolean.

item : JD.Decoder Item
item =
    JD.succeed Item
        |> R.field "title" JD.string
        |> R.field "done" JD.bool

The corresponding JSON would look like:

{
    "title": "Hello",
    "done": false
}

The decoder for Config which contains a list of Item.

config : JD.Decoder Config
config =
    JD.map Config
        (JD.field "list" (JD.list item))

The corresponding JSON would look like:

[
    {
        "title": "Hello",
        "done": false
    },
    {
        "title": "World",
        "done": true
    }
]

We would also require to encode the information of the data structures into JSON format so that we can pass it around easily. Let’s start by writing the encoder for Item. itemE function can be seen as a transformation from Item record to a JSON value.

itemE : Item -> JE.Value
itemE i =
    JE.object
        [ ( "title", JE.string i.title )
        , ( "done", JE.bool i.done )
        ]

The encoder of Config:

configE : Config -> JE.Value
configE c =
    JE.object [ ( "list", JE.list itemE c.list ) ]

configE function can be seen as a transformation from Config record to a JSON value.

Understanding how Storybook works

The job of storybook is to help the programmer see how each page looks/behaves when feeded with different input values.

** Concept of Story **

Each story is a page which we have created by seeding it with a particular set of values.

The data structure of Story in Realm/Storybook.elm looks like:

type alias Story =
    { title : String
    , pageTitle : String
    , id : String
    , elmId : String
    , config : JE.Value
    }

Let’s discuss what each field means. title & pageTitle is the name which we associate with each story to signify what it does. id is the unique identifier with the help of which we can distinguish one story from another. config contains the JSON data stored as JE.Value which we want to pass to the page which we wish to view. elmId is the Elm module which helps in viewing the config JSON value.

A list of stories belonging to a particular elmId are grouped together where each element represents a page but seeded with different data. We then use stories function to club multiple list of stories which returns value of data type List (String, List Story). Each element in this list represents a tuple wherein the first element represents the title of the page and the second element shows us the list of stories we have for that particular page. If we choose to add another page to our website, we would add a tuple (String, List Story) to the already existing list.

The index function of Storybook.elm returns a record aliased by Story for a particular Elm module named Pages.Index representing the file Pages/Index.elm. Storybook helps us in viewing the frontend of what we have created in Pages/ folder very easily by passing context information (recollect Story?) which is required by the page in order to build it. We pass the Config which contains the data needed for us to display the Index page as a JSON value by encoding the record Config using the function Index.configE which takes a Config object as a parameter and returns JSON value (Json.Encode.Value).

The Storybook app defined in Realm/Storybook.elm expects to be seeded with a record containing List ( String, List Story ) (which is built with the function stories and title.

Tests

All the frontend code lives in Pages/ directory. Index.elm is the file which renders the endpoint /. Tests go in the same directory. To test the index page we create Pages/IndexTest.elm. Instructions to execute the whole test suite live inside Test.elm. RT.app expects to be seeded with a record containing tests of type List RT.Test and title. Before running RT.Test, the database is always reset. Each RT.Test is composed of certain steps (type RT.Step) which need to be performed one after another. RT.Navigate takes two parameters namely (String, String) &String where the first one is defined in Pages/IndexTest.elm and the second one represents an action and constructs a URL along with the parameters passed. The init method inside Pages/IndexTest.elm helps in checking whether we have got the correct response after performing the HTTP request.

Realm ToDo App Walk through

Database models

On using the realm-starter.sh script one can get the barebones app ready locally. On browsing the directory dj/hello there would be a migrations folder and models.py. The models.py file contains a table Counter for demo and we would be removing that. We will then create a new table to store our ToDos using the lines:

class Todo(models.Model):
    title = models.CharField(max_length=500)
    done = models.IntegerField(default=0)

To remove the migrations directory, use the command:

$ rm -rf dj/hello/migrations
$ mkdir dj/hello/migrations

Then we need to create a new set of migrations using the command:

$ manage makemigrations
$ migrate

Now we need to remove the previous database file and the schema table and generate a new one by issuing the command:

$ recreatedb_sqlite

Index page

What do we want the user to see when he/she first browses to our website? We want to show them a list of ToDos with the ability to check & un-check a ToDo item along with the ability to delete a ToDo item. We want to show the cross icon to delete a ToDo item only when we hover over it. If we check the current state of the frontend/Pages/Index.elm we can see that a data structure Config is defined which helps to render two pieces of information namely message and count.

We start by modeling how we want to store data and thus define the data structures we would be using/passing around. Thus, let’s start with small pieces and build on them.

Data Structures

First one is the Item which is a record to store the details of a single ToDo item.

type alias Item =
    { title : String
    , done : Bool
    }

The we want to pass a list of Item (or ToDos) to the whole page so that it can render a list of ToDos, each with their own property. So we define Config.

type alias Config =
    { list : List Item
    }

Then we want to add feature that it will allow us to cancel a ToDo by showing a cross when we hover over it. So we need a place to store the information which can help us in achieving that goal. For this we choose to define Model.

type alias Model =
    { list : List Item
    , hover : Maybe Int
    }

Type Msg to denote what’s happening:

type Msg
    = Click Int
    | Delete Int
    | Hover Bool Int
    | OnError (Dict String String)

This would require to import the Dict module like: import Dict exposing (Dict)

Also let’s define a method to create ToDo objects and add it to the top most line of frontend/Pages/Index.elm to make it module Pages.Index exposing (Config, Model, Msg, app, main, todo):

todo : Int -> String -> Bool -> Item
todo id title done =
    { id = id, title = title, done = done }

To transmit the information stored in the data structures, we choose to use JSON. Thus, we write the JSON decoder first which will take a JSON value and parse it in a data structure that we understand and use in Elm. To use the JSON functionalities we need to add these two import lines:

import Json.Decode as JD
import Json.Encode as JE

Let’s start by writing the decoder for Item. This JSON contains two fields title which is a String and done which is a boolean.

item : JD.Decoder Item
item =
    JD.succeed Item
        |> R.field "index" JD.int
        |> R.field "title" JD.string
        |> R.field "done" JD.bool

The corresponding JSON would look like:

{
    "title": "Hello",
    "done": false
}

The decoder for Config which contains a list of Item.

config : JD.Decoder Config
config =
    JD.map Config
        (JD.field "list" (JD.list item))

The corresponding JSON would look like:

[
    {
        "title": "Hello",
        "done": false
    },
    {
        "title": "World",
        "done": true
    }
]

We would also require to encode the information of the data structures into JSON format so that we can pass it around easily. Let’s start by writing the encoder for Item. itemE function can be seen as a transformation from Item record to a JSON value.

itemE : Item -> JE.Value
itemE i =
    JE.object
        [ ( "index", JE.int i.id )
        , ( "title", JE.string i.title )
        , ( "done", JE.bool i.done )
        ]

The encoder of Config:

configE : Config -> JE.Value
configE c =
    JE.object [ ( "list", JE.list itemE c.list ) ]

configE function can be seen as a transformation from Config record to a JSON value.

UI

Now we will start writing the view and the helper functions which will help us in created the User Interface to browse and act on the data structures.

view:

view : Model -> Int -> E.Element Msg
view m width =
    E.column [ E.width E.fill, E.height E.fill ]
        [ heading
        , RU.yesno (List.isEmpty m.list) empty (list m) width
        , addTodoView width
        ]

This function uses Realm.Utils which can be imported with the line import Realm.Utils as RU exposing (edges). It also needs a System module which can be fetched from here: https://raw.githubusercontent.com/pranitbauva1997/realm-todo-app/master/frontend/System.elm, placed in frontend and imported using the line import System as S.

heading:

heading : E.Element Msg
heading =
    RU.text [ E.centerX, EF.size 24, EF.color S.gray2, E.padding 20 ] "todos"

This function requires us to import Element.Border using import Element.Border as EB.

empty:

empty : Int -> E.Element Msg
empty width =
    RU.text
        [ E.centerX
        , E.width (E.px width)
        , EB.width 1
        , EB.rounded 2
        , EB.color S.gray5
        , E.padding 10
        , EF.center
        ]
        "No ToDos, yay!"

list:

list : Model -> Int -> E.Element Msg
list m width =
    let
        filtered =
            List.filter .done m.list

        bottom =
            case filtered of
                [] ->
                    0

                _ ->
                    1
    in
    E.column
        [ E.centerX
        , E.width (E.px width)
        , EB.widthEach { edges | left = 1, top = 1, right = 1, bottom = bottom }
        , EB.rounded 2
        , EB.color S.gray5
        ]
        [ E.column
            [ E.height (E.fill |> E.maximum 300 |> E.minimum 30)
            , E.scrollbarY
            , E.width E.fill
            , EB.color S.gray5
            ]
            (List.map (itemView m) m.list)
        , footer m.list
        ]

This function requires us to import import Element.Events as EE. We would also require the module Emoji.elm which can be obtained from here: https://raw.githubusercontent.com/pranitbauva1997/realm-todo-app/master/frontend/Emoji.elm, placed in frontend/ and imported using import Emoji.

itemView:

itemView : Model -> Item -> E.Element Msg
itemView m i =
    E.row
        [ E.padding 10
        , EB.widthEach { edges | bottom = 1 }
        , EB.color S.gray5
        , E.width E.fill
        , E.spacing 5
        , EE.onMouseEnter (Hover True i.id)
        , EE.onMouseLeave (Hover False i.id)
        , E.pointer
        ]
        [ RU.text
            [ E.alignTop
            , RU.onClick (Click i.id)
            , RU.onDoubleClick (Click i.id)
            ]
          <|
            RU.yesno i.done Emoji.public Emoji.private
        , E.paragraph [] [ E.text i.title ]
        , RU.iff (m.hover == Just i.id) <|
            E.el [ E.alignRight, RU.onClick (Delete i.id), EF.size 12 ]
                (E.text Emoji.cross)
        ]

footer:

footer : List Item -> E.Element Msg
footer lst =
    let
        filtered =
            List.filter .done lst

        wrapper =
            RU.text [ E.padding 10, EF.color S.gray3, EF.size 14 ]

        title =
            case List.length filtered of
                0 ->
                    E.none

                1 ->
                    "1 item left"
                        |> wrapper

                _ ->
                    String.fromInt (List.length filtered)
                        ++ " items left"
                        |> wrapper
    in
    title

This requires the package Element.Input imported using import Element.Input as EI.

addTodoView:

addTodoView : Int -> E.Element Msg
addTodoView width =
    E.column
        [ E.centerX
        , E.width (E.px width)
        , EB.widthEach { edges | top = 20 }
        , EB.color S.white
        ]
        [ EI.button
            [ E.centerX
            ]
            { onPress = Nothing
            , label = E.text Emoji.plus
            }
        ]

subscriptions:

subscriptions : R.In -> Model -> Sub (R.Msg Msg)
subscriptions _ _ =
    Sub.none

document:

document : R.In -> Model -> B.Document (R.Msg Msg)
document in_ m =
    view m (min 500 (in_.width - 50))
        |> E.map R.Msg
        |> R.document in_

init:

init : R.In -> Config -> ( Model, Cmd (R.Msg Msg) )
init _ c =
    ( { list = c.list, hover = Maybe.Nothing }, Cmd.none )

update:

update : R.In -> Msg -> Model -> ( Model, Cmd (R.Msg Msg) )
update _ msg m =
    case msg of
        Click idx ->
            let
                modified =
                    RU.mapIth idx (\i -> { i | done = not i.done }) m.list
            in
            ( { m | list = modified }, Cmd.none )

        Delete idx ->
            ( { m | list = RU.deleteIth idx m.list }, Cmd.none )

        Hover open idx ->
            if open then
                ( { m | hover = Just idx }, Cmd.none )

            else
                ( { m | hover = Nothing }, Cmd.none )

        OnError _ ->
            ( m, Cmd.none )

app:

app : R.App Config Model Msg
app =
    R.App config init update subscriptions document

Basic Test

Let’s write a basic test to verify whether we get an empty ToDo or not when the DB is reset. In the frontend/Test.elm, add this function:

emptyList : ( String, String )
emptyList =
    ( "Index", "emptyList" )

In Routes.elm add the function index:

index : String
index =
    "/"

We would also require to change the top line in Index.elm to module Pages.Index exposing (Config, Model, Msg, app, main) so that we can use the data structure Model of Index page in other places.

File Test.elm

index:

index : List RT.Step
index =
    [ RT.Navigate Index.emptyList Routes.index
    ]

File IndexTest.elm

init:

init : R.In -> R.TestFlags M.Config -> ( M.Model, Cmd (R.Msg M.Msg) )
init in_ test =
    let
        id =
            ( "Index", test.id )

        ( m, c ) =
            M.app.init in_ test.config

        f : List R.TestResult -> ( M.Model, Cmd (R.Msg M.Msg) )
        f l =
            ( m, R.result c (l ++ [ R.TestDone ]) )
    in
    (if id == emptyList then
        [ RU.match "no todos" [] test.config.list ]

     else
        [ R.TestFailed test.id "IndexTest: id not known" ]
    )
        |> f

Backend Stuff

Remove the src/increment.rs file. Remove the line pub mod increment; from src/routes/mod.rs and replace the line ("/increment/", _) => realm_tutorial::routes::increment::get(in_), with ("/", _) => realm_tutorial::routes::index::get(in_), from src/main.rs.

File src/routes/index.rs

Add this after the imports:

#[derive(Serialize, Deserialize)]
pub struct Item {
    pub index: i32,
    pub title: String,
    pub done: bool,
}

Replace the following struct Page with the original one:

#[realm_page(id = "Pages.Index")]
struct Page {
    list: Vec<Item>,
}

Remove the function get. Define a new function index:

pub fn index(in_: &In0) -> realm::Result {
    Page {
        list: vec![]
    }
    .with_title("Welcome")
}

In the function redirect, get needs to be replaced with index.

Remove the entire contents of the src/db.rs except for the first line. Remove the contents of the file templates/pages/index.html except for the first line.

Let’s compile and check whether what we have done is correctly implemented.

To just check our rust code, try out:

$ cargo check

To get the server up and running, try:

$ cargo run -- --test

To compile the frontend elm code, try:

$ doit elm

Hurray! We have got the index page up and running.

Add ToDo item

In the file src/main.rs we have routing defined where we can add one more line in the match block:

("/add/", _) => realm_tutorial::routes::index::add(
            in_,
            input.required("title")?,
            input.required("done")?,
        ),

We have to now define the function add which will insert data into the database and return a realm::Result i. e. the index page to show all the ToDos in the file src/routes/index.rs.

pub fn add(in_: &In0, title: String, done: bool) -> realm::Result {
    add_db(in_, title, done as i32)?;
    redirect(in_)
}

We will now add the db helper in the file src/db.rs which will make the database connection using diesel and insert our row:

pub fn add_db(in_: &In0, title: String, done: i32) -> Result<()> {
    use crate::schema::hello_todo;
    use diesel::prelude::*;

    diesel::insert_into(hello_todo::table)
        .values((hello_todo::title.eq(title), hello_todo::done.eq(done)))
        .execute(in_.conn)
        .map(|_| ())
        .map_err(Into::into)
}

Get all ToDo items

Uptill now we have been returning 0 items by hard coding. Now we will actually fetch stuff from the DB and return the ToDos.

In the file src/db.rs we define get_all_db:

pub fn get_all_db(in_: &In0) -> Result<Vec<crate::routes::index::Item>> {
    use crate::schema::hello_todo;
    use diesel::prelude::*;

    let rows: Result<Vec<(i32, String, i32)>> = hello_todo::table
        .select((hello_todo::id, hello_todo::title, hello_todo::done))
        .load(in_.conn)
        .map_err(Into::into);

    match rows {
        Ok(r) => Ok(r
            .into_iter()
            .map(|item| crate::routes::index::Item {
                index: item.0,
                title: item.1,
                done: item.2 != 0,
            })
            .collect()),
        Err(er) => Err(er),
    }
}

In src/routes/index.rs, inside the index function where we construct the Page struct, we will modify the line list: vec![] to get_all_db(in_)?.

Delete ToDo

Implement the function delete_db inside src/db.rs:

pub fn delete_db(in_: &In0, id: i32) -> Result<()> {
    use crate::schema::hello_todo;
    use diesel::prelude::*;

    diesel::delete(hello_todo::dsl::hello_todo.filter(hello_todo::id.eq(id)))
        .execute(in_.conn)
        .map(|_| ())
        .map_err(Into::into)
}

Implement the function delete inside src/routes/index.rs:

pub fn delete(in_: &In0, index: i32) -> realm::Result {
    delete_db(in_, index)?;
    redirect(in_)
}

Then add the line ("/delete/", _) => realm_tutorial::routes::index::delete(in_, input.required("id")?), in src/main.rs to the match block (the routing table).

Toggle ToDo

In src/db.rs add:

pub fn toggle_db(in_: &In0, id: i32) -> Result<()> {
    use crate::schema::hello_todo;
    use diesel::prelude::*;

    let rows: Result<Vec<(i32, String, i32)>> = hello_todo::table
        .select((hello_todo::id, hello_todo::title, hello_todo::done))
        .filter(hello_todo::id.eq(id))
        .load(in_.conn)
        .map_err(Into::into);

    let updated_rows: Vec<(i32, String, i32)> = match rows {
        Ok(r) => r
            .into_iter()
            .map(|item| (item.0, item.1, if item.2 == 0 { 1 } else { 0 }))
            .collect(),
        Err(_) => vec![],
    };

    diesel::update(hello_todo::dsl::hello_todo.filter(hello_todo::id.eq(updated_rows[0].0)))
        .set((
            hello_todo::id.eq(updated_rows[0].0),
            hello_todo::title.eq(updated_rows[0].1.as_str()),
            hello_todo::done.eq(updated_rows[0].2),
        ))
        .execute(in_.conn)
        .map(|_| ())
        .map_err(Into::into)
}

In src/routes/index.rs add:

pub fn toggle(in_: &In0, index: i32) -> realm::Result {
    toggle_db(in_, index)?;
    redirect(in_)
}

In src/main.rs add:

("/toggle/", _) => realm_tutorial::routes::index::toggle(in_, input.required("id")?),

Send HTTP request to delete

Add tests to verify the functionality we have implemented

In frontend/Pages/IndexTest.elm add the following functions:

singleToDo : ( String, String )
singleToDo =
    ( "Index", "singleToDo" )


deleteToDo : ( String, String )
deleteToDo =
    ( "Index", "deleteToDo" )


twoToDos : ( String, String )
twoToDos =
    ( "Index", "twoToDos" )


toggleToDo : ( String, String )
toggleToDo =
    ( "Index", "toggleToDo" )

Also add this block to the if else block in the init function:

	 else if id == singleToDo then
        [ RU.match "single todo"
            [ M.todo 1 "Hello" False ]
            test.config.list
        ]

     else if id == deleteToDo then
        [ RU.match "single todo"
            [ M.todo 1 "Hello" False ]
            test.config.list
        ]

     else if id == twoToDos then
        [ RU.match "two todos"
            [ M.todo 1 "Hello" False
            , M.todo 2 "World" False
            ]
            test.config.list
        ]

     else if id == toggleToDo then
        [ RU.match "toggling todo"
            [ M.todo 1 "Hello" False
            , M.todo 2 "World" True
            ]
            test.config.list
        ]

Finally go to frontend/Test.elm and replace the index function with:

index : List RT.Step
index =
    [ RT.Navigate Index.emptyList Routes.index
    , RT.SubmitForm Index.singleToDo (Actions.addToDo "Hello" False)
    , RT.SubmitForm Index.twoToDos (Actions.addToDo "World" False)
    , RT.SubmitForm Index.toggleToDo (Actions.toggleToDo 2)
    , RT.SubmitForm Index.deleteToDo (Actions.deleteToDo 2)
    ]

Also add a few more functions:

resetDB : List RT.Step
resetDB =
    [ RT.Navigate Index.emptyList Routes.index ]


manual : List RT.Step
manual =
    [ RT.Navigate Index.emptyList Routes.index
    , RT.SubmitForm Index.singleToDo (Actions.addToDo "Hello" False)
    , RT.SubmitForm Index.twoToDos (Actions.addToDo "World" False)
    ]

Finally in the tests function replace the t declaration with this:

		t =
            [ f "index" index
            , f "resetDB" resetDB
            , f "manual" manual
            ]

Also add the import: import Actions to frontend/Test.elm.

Implement frontend/Actions.elm to construct the URLs for sending HTTP requests to:

module Actions exposing (..)

import Json.Encode as JE


s2 : String -> List ( String, JE.Value ) -> ( String, JE.Value )
s2 url params =
    ( url, JE.object params )


toggleToDo : Int -> ( String, JE.Value )
toggleToDo i =
    s2 "/toggle/" [ ( "id", JE.int i ) ]


addToDo : String -> Bool -> ( String, JE.Value )
addToDo title done =
    s2 "/add/"
        [ ( "title", JE.string title )
        , ( "done", JE.bool done )
        ]


deleteToDo : Int -> ( String, JE.Value )
deleteToDo i =
    s2 "/delete/" [ ( "id", JE.int i ) ]

Table Of Content

What is Realm?

A Bit On Motivation

Routing is Hard

What does Realm do?

Backend Data And Type Safety

Tutorial

Quick Start Realm Tutorial

In Depth Tutorial (not ready)

Nix
Shell
Doit
Hello Rust
Hello Elm
Hello Static Files
Hello Server Side Rendering
Pre-Commit Hooks

Routing, Request And Response

Frontend, Data, Navigation, And APIs

How To Guides

File Upload

Backend: S3 File Upload
Authenticated File Serving
Frontend: Uploading Files From Elm

How to use storybook?

How to implement “loading..”?

Docs

Realm.In

Realm.Storybook.Story

realm::In

realm::Context

realm::Result

realm.magicSlice

realm::RequestConfig

Environment Variables

Internals - Only for Realm Developers, not Users

“Realm DATA”
iFrame Controller
Shutdown Routine
Testing Internals

Change Log

Get Realm Starter Working

Transparent Offline Feature

How to make http requests in Realm?

Development

Replay Testing

Tutorial: ToDo App

Realm Testing

Enhance Realm Starter

Double Load Issue

Deploy To Heroku Button

End failure

Realm-Starter Github Template

Proposal: Tracker And Visit

Proposal: Activity Store

Proposal: Bundling

Proposal: Retry On Network Error

Storybook: Editable JSON

Storybook: Notes

Storybook: Reference

Backlog

Readings

Change Log

How to Publish

Testing

Code Snippets

Skip rustfmt For Some Section

Close Modal Dialog When Clicked Outside

Ignoring Lints In Python

Ignoring Lints (clippy and rustc warnings) In Rust

Handle DateTime in Rust & Elm

Handle CiText value read in Rust

Transport Enum Type to and fro Rust/Elm through JSON