diff options
author | William Carroll <wpcarro@gmail.com> | 2020-12-11T22·47+0000 |
---|---|---|
committer | William Carroll <wpcarro@gmail.com> | 2020-12-11T22·47+0000 |
commit | 9e2fbfde8e9c2256e1817f79d119e0dd7bed7b7d (patch) | |
tree | 6010b135c9c466e5dee21c8e4e5c85f47529a35f /scratch | |
parent | 3feb8ceb9a6bbaa48927188de4806c8573f499a5 (diff) |
Move the habit-screens project into //website
I'd like to eventually deploy this to wpcarro.dev. Coming soon!
Diffstat (limited to 'scratch')
-rw-r--r-- | scratch/habit-screens/README.md | 12 | ||||
-rw-r--r-- | scratch/habit-screens/client/.envrc | 2 | ||||
-rw-r--r-- | scratch/habit-screens/client/.gitignore | 3 | ||||
-rw-r--r-- | scratch/habit-screens/client/README.md | 18 | ||||
-rw-r--r-- | scratch/habit-screens/client/elm.json | 32 | ||||
-rw-r--r-- | scratch/habit-screens/client/index.css | 3 | ||||
-rw-r--r-- | scratch/habit-screens/client/index.html | 21 | ||||
-rw-r--r-- | scratch/habit-screens/client/shell.nix | 10 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/Habits.elm | 465 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/Main.elm | 29 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/State.elm | 195 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/UI.elm | 9 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/Utils.elm | 37 | ||||
-rw-r--r-- | scratch/habit-screens/design.md | 43 |
14 files changed, 0 insertions, 879 deletions
diff --git a/scratch/habit-screens/README.md b/scratch/habit-screens/README.md deleted file mode 100644 index f0a5be9cabf8..000000000000 --- a/scratch/habit-screens/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Habit Screens - -Problem: I would like to increase the rate at which I complete my daily, weekly, -monthly, yearly habits. - -Solution: Habit Screens are mounted in strategic locations throughout my -apartment. Each Habit Screen displays the habits that I should complete that -day, and I can tap each item to mark it as complete. I will encounter the Habit -Screens in my bedroom, kitchen, and bathroom, so I will have adequate "cues" to -focus my attention. By marking each item as complete and tracking the results -over time, I will have more incentive to maintain my consistency -(i.e. "reward"). diff --git a/scratch/habit-screens/client/.envrc b/scratch/habit-screens/client/.envrc deleted file mode 100644 index a4a62da526d3..000000000000 --- a/scratch/habit-screens/client/.envrc +++ /dev/null @@ -1,2 +0,0 @@ -source_up -use_nix diff --git a/scratch/habit-screens/client/.gitignore b/scratch/habit-screens/client/.gitignore deleted file mode 100644 index 1cb4f3034cc3..000000000000 --- a/scratch/habit-screens/client/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/elm-stuff -/Main.min.js -/output.css diff --git a/scratch/habit-screens/client/README.md b/scratch/habit-screens/client/README.md deleted file mode 100644 index 04804ad94fac..000000000000 --- a/scratch/habit-screens/client/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Elm - -Elm has one of the best developer experiences that I'm aware of. The error -messages are helpful and the entire experience is optimized to improve the ease -of writing web applications. - -## Developing - -If you're interested in contributing, the following will create an environment -in which you can develop: - -```shell -$ nix-shell -$ npx tailwindcss build index.css -o output.css -$ elm-live -- src/Main.elm --output=Main.min.js -``` - -You can now view your web client at `http://localhost:8000`! diff --git a/scratch/habit-screens/client/elm.json b/scratch/habit-screens/client/elm.json deleted file mode 100644 index 6839ac4fabdc..000000000000 --- a/scratch/habit-screens/client/elm.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "type": "application", - "source-directories": [ - "src" - ], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "elm/browser": "1.0.2", - "elm/core": "1.0.5", - "elm/html": "1.0.0", - "elm/random": "1.0.0", - "elm/svg": "1.0.1", - "elm/time": "1.0.0", - "elm-community/list-extra": "8.2.3", - "elm-community/maybe-extra": "5.2.0", - "elm-community/random-extra": "3.1.0", - "justinmimbs/date": "3.2.1" - }, - "indirect": { - "elm/json": "1.1.3", - "elm/parser": "1.1.0", - "elm/url": "1.0.0", - "elm/virtual-dom": "1.0.2", - "owanturist/elm-union-find": "1.0.0" - } - }, - "test-dependencies": { - "direct": {}, - "indirect": {} - } -} diff --git a/scratch/habit-screens/client/index.css b/scratch/habit-screens/client/index.css deleted file mode 100644 index b5c61c956711..000000000000 --- a/scratch/habit-screens/client/index.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/scratch/habit-screens/client/index.html b/scratch/habit-screens/client/index.html deleted file mode 100644 index b587e0901284..000000000000 --- a/scratch/habit-screens/client/index.html +++ /dev/null @@ -1,21 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8" /> - <title>Elm SPA</title> - <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Chilanka"> - <link rel="stylesheet" href="./output.css"> - <style> - body { - font-family: 'Chilanka'; - } - </style> - <script src="./Main.min.js"></script> - </head> - <body> - <div id="mount"></div> - <script> - Elm.Main.init({node: document.getElementById("mount")}); - </script> - </body> -</html> diff --git a/scratch/habit-screens/client/shell.nix b/scratch/habit-screens/client/shell.nix deleted file mode 100644 index 00bb4b0b3edc..000000000000 --- a/scratch/habit-screens/client/shell.nix +++ /dev/null @@ -1,10 +0,0 @@ -let - briefcase = import <briefcase> {}; - pkgs = briefcase.third_party.pkgs; -in pkgs.mkShell { - buildInputs = with pkgs.elmPackages; [ - elm - elm-format - elm-live - ]; -} diff --git a/scratch/habit-screens/client/src/Habits.elm b/scratch/habit-screens/client/src/Habits.elm deleted file mode 100644 index bbd5887f8bd5..000000000000 --- a/scratch/habit-screens/client/src/Habits.elm +++ /dev/null @@ -1,465 +0,0 @@ -module Habits exposing (render) - -import Browser -import Date exposing (Date) -import Html exposing (..) -import Html.Attributes exposing (..) -import Html.Events exposing (..) -import Set exposing (Set) -import State exposing (HabitType(..)) -import Time exposing (Weekday(..)) -import UI -import Utils exposing (Strategy(..)) - - -morning : List State.Habit -morning = - List.map - (\( duration, x ) -> - { label = x - , habitType = State.Morning - , minutesDuration = duration - } - ) - [ ( 1, "Make bed" ) - , ( 2, "Brush teeth" ) - , ( 10, "Shower" ) - , ( 1, "Do push-ups" ) - , ( 10, "Meditate" ) - ] - - -evening : List State.Habit -evening = - List.map - (\( duration, x ) -> - { label = x - , habitType = State.Evening - , minutesDuration = duration - } - ) - [ ( 30, "Read" ) - , ( 1, "Record in habit Journal" ) - ] - - -monday : List ( Int, String ) -monday = - [ ( 90, "Bikram Yoga @ 17:00" ) - ] - - -tuesday : List ( Int, String ) -tuesday = - [ ( 90, "Bikram Yoga @ 18:00" ) - ] - - -wednesday : List ( Int, String ) -wednesday = - [ ( 5, "Shave" ) - , ( 90, "Bikram Yoga @ 17:00" ) - ] - - -thursday : List ( Int, String ) -thursday = - [] - - -friday : List ( Int, String ) -friday = - [ ( 60, "Bikram Yoga @ 17:00" ) - , ( 3, "Take-out trash" ) - , ( 60, "Shop for groceries" ) - ] - - -saturday : List ( Int, String ) -saturday = - [ ( 60, "Warm Yin Yoga @ 15:00" ) - ] - - -sunday : List ( Int, String ) -sunday = - [ ( 1, "Shampoo" ) - , ( 5, "Shave" ) - , ( 1, "Trim nails" ) - , ( 1, "Combine trash cans" ) - , ( 10, "Mop tile and wood floors" ) - , ( 10, "Laundry" ) - , ( 5, "Vacuum bedroom" ) - , ( 5, "Dust surfaces" ) - , ( 5, "Clean mirrors" ) - , ( 5, "Clean desk" ) - ] - - -payday : List State.Habit -payday = - List.map - (\( duration, x ) -> - { label = x - , habitType = State.Payday - , minutesDuration = duration - } - ) - [ ( 1, "Ensure \"Emergency\" fund has a balance of 1000 GBP" ) - , ( 1, "Open \"finances_2020\" Google Sheet" ) - , ( 1, "Settle up with Mimi on TransferWise" ) - , ( 1, "Adjust GBP:USD exchange rate" ) - , ( 1, "Adjust \"Stocks (after tax)\" to reflect amount Google sent" ) - , ( 1, "Add remaining cash to \"Carryover (cash)\"" ) - , ( 1, "Adjust \"Paycheck\" to reflect amount Google sent" ) - , ( 5, "In the \"International Xfer\" table, send \"Xfer amount\" from Monzo to USAA" ) - , ( 10, "Go to an ATM and extract the amount in \"ATM withdrawal\"" ) - , ( 0, "Await the TransferWise transaction to complete and pay MyFedLoan in USD" ) - ] - - -firstOfTheMonth : List State.Habit -firstOfTheMonth = - List.map - (\( duration, x ) -> - { label = x - , habitType = State.FirstOfTheMonth - , minutesDuration = duration - } - ) - [ ( 10, "Create habit template in journal" ) - , ( 30, "Assess previous month's performance" ) - , ( 5, "Register for Bikram Yoga classes" ) - ] - - -firstOfTheYear : List State.Habit -firstOfTheYear = - List.map - (\( duration, x ) -> - { label = x - , habitType = State.FirstOfTheYear - , minutesDuration = duration - } - ) - [ ( 60, "Write a post mortem for the previous year" ) - ] - - -habitTypes : - { includeMorning : Bool - , includeEvening : Bool - , date : Date - } - -> List State.HabitType -habitTypes { includeMorning, includeEvening, date } = - let - habitTypePredicates : List ( State.HabitType, Date -> Bool ) - habitTypePredicates = - [ ( Morning, \_ -> includeMorning ) - , ( DayOfWeek, \_ -> True ) - , ( Payday, \x -> Date.day x == 25 ) - , ( FirstOfTheMonth, \x -> Date.day x == 1 ) - , ( FirstOfTheYear, \x -> Date.day x == 1 && Date.monthNumber x == 1 ) - , ( Evening, \_ -> includeEvening ) - ] - in - habitTypePredicates - |> List.filter (\( _, predicate ) -> predicate date) - |> List.map (\( habitType, _ ) -> habitType) - - -habitsFor : State.HabitType -> Weekday -> List State.Habit -habitsFor habitType weekday = - case habitType of - Morning -> - morning - - Evening -> - evening - - DayOfWeek -> - let - toHabit : List ( Int, String ) -> List State.Habit - toHabit = - List.map - (\( duration, x ) -> - { label = x - , habitType = State.DayOfWeek - , minutesDuration = duration - } - ) - in - case weekday of - Mon -> - toHabit monday - - Tue -> - toHabit tuesday - - Wed -> - toHabit wednesday - - Thu -> - toHabit thursday - - Fri -> - toHabit friday - - Sat -> - toHabit saturday - - Sun -> - toHabit sunday - - Payday -> - payday - - FirstOfTheMonth -> - firstOfTheMonth - - FirstOfTheYear -> - firstOfTheYear - - -weekdayLabelFor : Weekday -> State.WeekdayLabel -weekdayLabelFor weekday = - case weekday of - Mon -> - "Monday" - - Tue -> - "Tuesday" - - Wed -> - "Wednesday" - - Thu -> - "Thursday" - - Fri -> - "Friday" - - Sat -> - "Saturday" - - Sun -> - "Sunday" - - -timeRemaining : State.WeekdayLabel -> State.CompletedHabits -> List State.Habit -> Int -timeRemaining weekdayLabel completed habits = - habits - |> List.indexedMap - (\i { label, minutesDuration } -> - if Set.member ( weekdayLabel, label ) completed then - 0 - - else - minutesDuration - ) - |> List.sum - - -render : State.Model -> Html State.Msg -render { today, visibleDayOfWeek, completed, includeMorning, includeEvening } = - case ( today, visibleDayOfWeek ) of - ( Just todaysDate, Just visibleWeekday ) -> - let - todaysWeekday : Weekday - todaysWeekday = - Date.weekday todaysDate - - habits : List State.Habit - habits = - habitTypes - { includeMorning = includeMorning - , includeEvening = includeEvening - , date = todaysDate - } - |> List.map (\habitType -> habitsFor habitType todaysWeekday) - |> List.concat - in - div - [ Utils.class - [ Always "max-w-xl mx-auto py-6 px-6" - , When (todaysWeekday /= visibleWeekday) "pt-20" - ] - ] - [ header [] - [ if todaysWeekday /= visibleWeekday then - div [ class "text-center w-full bg-blue-600 text-white fixed top-0 left-0 px-3 py-4" ] - [ p [ class "py-2 inline pr-5" ] - [ text "As you are not viewing today's habits, the UI is in read-only mode" ] - , UI.button - [ class "bg-blue-200 px-4 py-2 rounded text-blue-600 text-xs font-bold" - , onClick State.ViewToday - ] - [ text "View Today's Habits" ] - ] - - else - text "" - , div [ class "flex center" ] - [ UI.button - [ class "w-1/4 text-gray-500" - , onClick State.ViewPrevious - ] - [ text "‹ previous" ] - , h1 [ class "font-bold text-blue-500 text-3xl text-center w-full" ] - [ text (weekdayLabelFor visibleWeekday) ] - , UI.button - [ class "w-1/4 text-gray-500" - , onClick State.ViewNext - ] - [ text "next ›" ] - ] - ] - , if todaysWeekday == visibleWeekday then - p [ class "text-center pt-1 pb-4" ] - [ let - t : Int - t = - timeRemaining (weekdayLabelFor todaysWeekday) completed habits - in - if t == 0 then - text "Nothing to do!" - - else - text - ((habits - |> timeRemaining (weekdayLabelFor todaysWeekday) completed - |> String.fromInt - ) - ++ " minutes remaining" - ) - ] - - else - text "" - , if todaysWeekday == visibleWeekday then - div [] - [ UI.button - [ onClick - (if Set.size completed == 0 then - State.DoNothing - - else - State.ClearAll - ) - , Utils.class - [ Always "ml-10 px-3" - , If (Set.size completed == 0) - "text-gray-500 cursor-not-allowed" - "text-red-500 underline cursor-pointer" - ] - ] - [ let - numCompleted : Int - numCompleted = - habits - |> List.indexedMap (\i { label } -> ( i, label )) - |> List.filter - (\( i, label ) -> - Set.member - ( weekdayLabelFor todaysWeekday, label ) - completed - ) - |> List.length - in - if numCompleted == 0 then - text "Clear" - - else - text ("Clear (" ++ String.fromInt numCompleted ++ ")") - ] - , UI.button - [ onClick State.ToggleMorning - , Utils.class - [ Always "px-3 underline" - , If includeMorning - "text-gray-600" - "text-blue-600" - ] - ] - [ text - (if includeMorning then - "Hide Morning" - - else - "Show Morning" - ) - ] - , UI.button - [ Utils.class - [ Always "px-3 underline" - , If includeEvening - "text-gray-600" - "text-blue-600" - ] - , onClick State.ToggleEvening - ] - [ text - (if includeEvening then - "Hide Evening" - - else - "Show Evening" - ) - ] - ] - - else - text "" - , ul [ class "pb-10" ] - (habits - |> List.indexedMap - (\i { label, minutesDuration } -> - let - isCompleted : Bool - isCompleted = - Set.member ( weekdayLabelFor todaysWeekday, label ) completed - in - li [ class "text-xl list-disc ml-6" ] - [ if todaysWeekday == visibleWeekday then - UI.button - [ class "py-5 px-3" - , onClick - (State.ToggleHabit - (weekdayLabelFor todaysWeekday) - label - ) - ] - [ span - [ Utils.class - [ Always "text-white pt-1 px-2 rounded" - , If isCompleted "bg-gray-400" "bg-blue-500" - ] - ] - [ text (String.fromInt minutesDuration ++ " mins") ] - , p - [ Utils.class - [ Always "inline pl-3" - , When isCompleted "line-through text-gray-400" - ] - ] - [ text label ] - ] - - else - UI.button - [ class "py-5 px-3 cursor-not-allowed" - , onClick State.DoNothing - ] - [ text label ] - ] - ) - ) - , footer [ class "bg-white text-sm text-center text-gray-500 fixed bottom-0 left-0 w-full py-4" ] - [ p [] [ text "This app is brought to you by William Carroll." ] - , p [] [ text "Client: Elm; Server: n/a" ] - ] - ] - - ( _, _ ) -> - p [] [ text "Unable to display habits because we do not know what day of the week it is." ] diff --git a/scratch/habit-screens/client/src/Main.elm b/scratch/habit-screens/client/src/Main.elm deleted file mode 100644 index 2ddedb913357..000000000000 --- a/scratch/habit-screens/client/src/Main.elm +++ /dev/null @@ -1,29 +0,0 @@ -module Main exposing (main) - -import Browser -import Habits -import Html exposing (..) -import State -import Time - - -subscriptions : State.Model -> Sub State.Msg -subscriptions model = - -- once per minute - Time.every (1000 * 60) (\_ -> State.MaybeAdjustWeekday) - - -view : State.Model -> Html State.Msg -view model = - case model.view of - State.Habits -> - Habits.render model - - -main = - Browser.element - { init = \() -> State.init - , subscriptions = subscriptions - , update = State.update - , view = view - } diff --git a/scratch/habit-screens/client/src/State.elm b/scratch/habit-screens/client/src/State.elm deleted file mode 100644 index c75c99322249..000000000000 --- a/scratch/habit-screens/client/src/State.elm +++ /dev/null @@ -1,195 +0,0 @@ -module State exposing (..) - -import Date exposing (Date) -import Set exposing (Set) -import Task -import Time exposing (Weekday(..)) - - -type alias WeekdayLabel = - String - - -type alias HabitLabel = - String - - -type Msg - = DoNothing - | SetView View - | ReceiveDate Date - | ToggleHabit WeekdayLabel HabitLabel - | MaybeAdjustWeekday - | ViewToday - | ViewPrevious - | ViewNext - | ClearAll - | ToggleMorning - | ToggleEvening - - -type View - = Habits - - -type HabitType - = Morning - | Evening - | DayOfWeek - | Payday - | FirstOfTheMonth - | FirstOfTheYear - - -type alias Habit = - { label : HabitLabel - , habitType : HabitType - , minutesDuration : Int - } - - -type alias CompletedHabits = - Set ( WeekdayLabel, HabitLabel ) - - -type alias Model = - { isLoading : Bool - , view : View - , today : Maybe Date - , completed : CompletedHabits - , visibleDayOfWeek : Maybe Weekday - , includeMorning : Bool - , includeEvening : Bool - } - - -previousDay : Weekday -> Weekday -previousDay weekday = - case weekday of - Mon -> - Sun - - Tue -> - Mon - - Wed -> - Tue - - Thu -> - Wed - - Fri -> - Thu - - Sat -> - Fri - - Sun -> - Sat - - -nextDay : Weekday -> Weekday -nextDay weekday = - case weekday of - Mon -> - Tue - - Tue -> - Wed - - Wed -> - Thu - - Thu -> - Fri - - Fri -> - Sat - - Sat -> - Sun - - Sun -> - Mon - - -{-| The initial state for the application. --} -init : ( Model, Cmd Msg ) -init = - ( { isLoading = False - , view = Habits - , today = Nothing - , completed = Set.empty - , visibleDayOfWeek = Nothing - , includeMorning = False - , includeEvening = False - } - , Date.today |> Task.perform ReceiveDate - ) - - -{-| Now that we have state, we need a function to change the state. --} -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg ({ today, visibleDayOfWeek, completed } as model) = - case msg of - DoNothing -> - ( model, Cmd.none ) - - SetView x -> - ( { model - | view = x - , isLoading = True - } - , Cmd.none - ) - - ReceiveDate x -> - ( { model - | today = Just x - , visibleDayOfWeek = Just (Date.weekday x) - } - , Cmd.none - ) - - ToggleHabit weekdayLabel habitLabel -> - ( { model - | completed = - if Set.member ( weekdayLabel, habitLabel ) completed then - Set.remove ( weekdayLabel, habitLabel ) completed - - else - Set.insert ( weekdayLabel, habitLabel ) completed - } - , Cmd.none - ) - - MaybeAdjustWeekday -> - ( model, Date.today |> Task.perform ReceiveDate ) - - ViewToday -> - ( { model | visibleDayOfWeek = today |> Maybe.map Date.weekday }, Cmd.none ) - - ViewPrevious -> - ( { model - | visibleDayOfWeek = visibleDayOfWeek |> Maybe.map previousDay - } - , Cmd.none - ) - - ViewNext -> - ( { model - | visibleDayOfWeek = visibleDayOfWeek |> Maybe.map nextDay - } - , Cmd.none - ) - - ClearAll -> - ( { model | completed = Set.empty }, Cmd.none ) - - ToggleMorning -> - ( { model | includeMorning = not model.includeMorning }, Cmd.none ) - - ToggleEvening -> - ( { model | includeEvening = not model.includeEvening }, Cmd.none ) diff --git a/scratch/habit-screens/client/src/UI.elm b/scratch/habit-screens/client/src/UI.elm deleted file mode 100644 index 5b5426913570..000000000000 --- a/scratch/habit-screens/client/src/UI.elm +++ /dev/null @@ -1,9 +0,0 @@ -module UI exposing (..) - -import Html exposing (..) -import Html.Attributes exposing (..) - - -button : List (Attribute msg) -> List (Html msg) -> Html msg -button attrs children = - Html.button ([ class "focus:outline-none" ] ++ attrs) children diff --git a/scratch/habit-screens/client/src/Utils.elm b/scratch/habit-screens/client/src/Utils.elm deleted file mode 100644 index 23b13c224c68..000000000000 --- a/scratch/habit-screens/client/src/Utils.elm +++ /dev/null @@ -1,37 +0,0 @@ -module Utils exposing (..) - -import Html exposing (..) -import Html.Attributes exposing (..) -import Maybe.Extra - - -type Strategy - = Always String - | When Bool String - | If Bool String String - - -class : List Strategy -> Attribute msg -class classes = - classes - |> List.map - (\strategy -> - case strategy of - Always x -> - Just x - - When True x -> - Just x - - When False _ -> - Nothing - - If True x _ -> - Just x - - If False _ x -> - Just x - ) - |> Maybe.Extra.values - |> String.join " " - |> Html.Attributes.class diff --git a/scratch/habit-screens/design.md b/scratch/habit-screens/design.md deleted file mode 100644 index f16361ac4358..000000000000 --- a/scratch/habit-screens/design.md +++ /dev/null @@ -1,43 +0,0 @@ -# Habit Screens - -## MVP - -One Android tablet mounted on my bedroom wall displaying habits for that day. I -can toggle the done/todo states on each item by tapping it. There is no -server. All of the habits are defined in the client-side codebase. The -application is available online at wpcarro.dev. - -## Ideal - -Three Android tablets: one mounted in my bedroom, another in my bathroom, and a -third in my kitchen. Each tablet has a view of the current state of the -application and updates in soft real-time. - -I track the rates at which I complete each habit and compile all of the metrics -into a dashboard. When I move a habit from Saturday to Sunday or from Wednesday -to Monday, it doesn't break the tracking. - -When I complete a habit, it quickly renders some consistency information like -"completing rate since Monday" and "length of current streak". - -I don't consider this application that sensitive, but for security purposes I -would like this application to be accessible within a private network. This is -something I don't know too much about setting up, but I don't want anyone to be -able to visit www.BillAndHisHabits.com and change the states of my habits and -affect the tracking data. Nor do I want anyone to be able to make HTTP requests -to my server to alter the state of the application without my permission. - -## Client - -Language: Elm - -### Updates across devices - -Instead of setting up sockets on my server and subscribing to them from the -client, I think each device should poll the server once every second (or fewer) -to maintain UI consistency. - -## Server - -Language: Haskell -Database: SQLite |