Fork me on GitHub
#carry2016-06-29
>
kauko10:06:47

@metametadata: is there a way to change the state of a carry app through the REPL (or in general, programmatically)? I'm asking because devcards' history feature works by saving snapshots of the "data atom" to a "history atom", and then resetting the data atom when the user clicks on the rewind button

kauko10:06:24

but since carry's model atom is actually read-only (EntangledReference), I don't think this can work

kauko10:06:26

inspecting the card's data atom's state is straight forward to implement, just have to make devcards accept atoms that are read-only if history is not on

metametadata11:06:01

Hey @kauko! The idea of Carry API was to force a user to only change model through the signals

kauko11:06:55

is the signal log available from the REPL?

metametadata11:06:37

so that's how state can be changed from the REPL: ((:dispatch-signal app) some-model-changing-signal)

metametadata11:06:44

signal log is available only if you explicitly save it somehow, e.g. via carry-debugger middleware - it saves the log of actions and signals into the model under ::debugger key

metametadata11:06:02

I'm not familiar with all devcards features, but maybe you could add a [:reset state] signal to your app and let Devcards call this signal instead of directly resetting "data atom"?

kauko11:06:05

I'd rather think of some way to make devcards support carry's model, than assume that users want to add explicit devcards support to their app 🙂

kauko11:06:23

TBH I don't think the history thing is that important, data inspection is much more important

kauko11:06:30

but history would be nice obviously

metametadata11:06:05

I don't quite imagine what you mean but I'd like to see how it works 🙂

metametadata11:06:58

another idea is to use carry-debugger instead of devcards history feature, but we will need to simplify debugger's UI to fit into the devcards pages, e.g. by showing only back/forward buttons in the small widget..

kauko11:06:23

The debugger will need to be split to model, reagent-view and rum-view namespaces anyway, so maybe if it had a devcards-view, that could be used from devcards

kauko11:06:01

Devcards has some Reagent specific macros, and I was thinking of adding some for Carry too

kauko11:06:15

the macro could maybe set up the debugger

kauko11:06:16

or.. add a :carry-debugger option to devcards. I'll have to talk to bruce about this at some point

metametadata11:06:53

I doubt he'll be happy about bloating the devcards just to support some new frontend framework )

metametadata11:06:30

Another idea: augment your "data atom" reference with ISwap using specify! in order to make it send a special [:swap new-state] signal to your app. This way devcards rewind button will call swap! on the "data atom" and it will lead to sending the model swap signal.

kauko11:06:14

Wouldn't that signal still need to be defined in the app somewhere?

kauko11:06:30

Actually, how does the debugger implement time travel? Or does it?

kauko11:06:33

Does it only allow you to go back to the initial model, and then replay all the actions that took you there?

kauko11:06:42

side note (though probably a pretty important topic): should the dispatch log be available even without the debugger? Having that available in a prod environment seems pretty powerful, you could serialize it and send it over whenever you have an error

metametadata11:06:27

yes. that signal would need to be additionally defined

metametadata11:06:12

debugger is a middleware which catches all bypassing signals and actions. than it replays the actions starting from the initial model value to implement the time travel

metametadata11:06:00

(so signals are not really replayed - only the action dispatched from signals)

metametadata12:06:58

I agree, saving a log in production and recording it to debug real errors seems like a nice idea

metametadata12:06:48

but I'd like to not make it a part of Carry core, an optional middleware would be better

metametadata12:06:30

I even think the extracted carry-debugger-model (name is TBD) middleware will suffice to make it work

kauko12:06:12

carry-signal-log? 🙂

metametadata12:06:11

I dunno ) I'd like it to be something about a debugger as it will also add all the debugger's signal handlers

kauko12:06:11

But if you use that to save the signal log in a prod environment, shouldn't the name reflect that somehow? You're not really debugging in prod 😛

metametadata12:06:57

I agree, but I'd like to not have 2 separate middleware with a lot of duplicated code

kauko12:06:44

Hmm I think I'm misunderstanding you. I thought you meant that you'd split the debugger's model into its own middleware, that could be used for the prod thingy, and if you want to use the debugger, you'd add that middleware AND a debugger-ui middleware of your choosing?

metametadata12:06:54

well, and one can actually imagine it to be a "debugging", just an offline one )

metametadata12:06:32

I guess you understand me correctly, let me write it down again so that we are on the same page..

metametadata12:06:45

1) At the moment there's a carry-debugger middleware implemented - it catches signals and actions, is able to time travel, save and load logs etc. It also comes with a Reagent UI (a sidebar with buttons and stuff) 2) We'd like to split it into several middleware: carry-debugger-core (or carry-debugger-model or whatever) and separate UIs for Reagent, Rum etc. in order to make it work in Carry apps with different UI libs. 3) We'd also like to be able to record user sessions in order to send them to server on errors. This can be implemented on top of carry-debugger-core middleware.

metametadata12:06:48

for a moment I thought you wanted to also implement signal-log middleware in addition to debugger-core one

kauko12:06:51

When you say "implemented on top of", what do you mean? What would the final solution look like to a dev? Let's say I want to have a debugger UI and send errors etc to the server, would I have something like (-> app (debugger-model/add) (debugger-rum-ui/add) (some-third-middleware??/add))?

metametadata12:06:40

because the way you send logs to server is specific to your app

metametadata12:06:59

maybe you send them to Sentry or maybe your own server

kauko14:06:21

@metametadata: haha, actually Carry worked with devcards straight out of the box 🙂

kauko14:06:59

Just had to do this

kauko14:06:59

So basically we just need something like carry/app, but it needs to take an atom as the initial model

kauko14:06:08

so devcards can use the same atom as its data atom

kauko14:06:54

Oh wow, even the history seems to work!

kauko14:06:37

@metametadata: I'm thinking, maybe the writable model atom should be returned in the application spec in some form. It's a good idea to not be able to reset it at whim in your code, but for development and debugging it could be handy to be able to access it from the REPL

kauko14:06:49

if I have some faulty data in my model, I'd like to be able to edit it

metametadata15:06:38

would it help if app returned :-writable-model or smt. in addition to :model and :dispatch-signal?

metametadata15:06:19

I'd like to not add magic hidden keys if possible in this case because it's possible to edit the model explicitly by simply defining [:reset new-state] signal in your app

metametadata15:06:48

or one can even create a utility middleware/function which exposes a "writable" app model for debugging

metametadata15:06:42

I'll try to play with devcards today and see if we can make it work without changing the core API much

kauko15:06:01

This would be enough to make carry work with devcards

kauko15:06:12

Devcards would call -build-app directly

kauko15:06:38

but honestly, all it needs is a way to provide the initial model as an atom, not a value

kauko15:06:40

then it works

kauko15:06:23

The writable atom / REPL thing I mentioned is not really related to devcards, it's another concern 🙂

kauko15:06:16

But actually the :reset signal is a good point

metametadata16:06:12

I have a feeling that we can make it work without an additional -build-app function

kauko16:06:55

Great! How?

metametadata16:06:34

we can make devcard's state atom a "proxy" which calls ((:dispatch-signal app) [:reset ...])

metametadata16:06:13

or maybe not )

metametadata16:06:17

I'll try to do that

metametadata16:06:13

so that user will only have to call an utility helper function to bind Carry and Devcards together

kauko16:06:07

Sounds interesting! Is it possible to add a "generic" [:reset ..] signal? One that is always available in a carry app?

metametadata16:06:21

it can be easily added via a middleware

kauko16:06:52

Yeah, I'm just wondering whether it should be added automatically

metametadata16:06:16

ah, I'd rather force user to do it explicitly

kauko16:06:58

Yeah that probably makes sense, I just brought this up because I know I would've come across that need very soon

kauko16:06:22

and if it's a need that 95% of devs have, it could make sense to just have it available

kauko16:06:27

and look as "familiar" as possible

kauko16:06:25

but I mean, it's your call. 🙂 Just bouncing ideas

metametadata16:06:37

And I appreciate your input a lot! I agree about 95% but it's hard to tell at the moment how many devs would need it ))

metametadata16:06:12

another possible scenario is that most devs would like to dispatch actions instead of editing a model by hand

metametadata16:06:45

and for some time there was a hidden :-dispatch-action available for this purpose

metametadata16:06:34

but then I decided to delete it to keep API smaller and bc it's possible to simulate it using an additional signal

kauko16:06:57

I wonder if there should be something like a carry.repl middleware

kauko16:06:02

that adds these sorts of things

metametadata16:06:17

let's see how it goes

metametadata16:06:41

and add it when it feels like it's really commonly useful

kauko16:06:23

btw, my reason for wanting to reset! and swap! the model directly is related mostly to filtering out bad data. In my current project I do that all the time. We draw stuff on a map and these things are defined as clojure data structures, so sometimes when there's a bug it's really handy to only draw things that have :type :multiline for example 🙂

kauko16:06:36

So the easier I can see the data, and change the data through the REPL, the happier I will be 😛

kauko16:06:46

some solutions hide it too much

metametadata16:06:07

OK, cool example, it's the kind of scenario which doesn't include dispatching actions

kauko16:06:33

Yeah, just what data is in the model 🙂

kauko16:06:49

Another thing, I'm really liking Carry. Enough to want to contribute to it. Is there anything I can help with? I was thinking the devcards thing would be "my thing", but I'm happy to let you do it if you have ideas 🙂 I understand if you feel like you'd rather work on it yourself mostly

kauko16:06:37

I'll probably start working on a series of blog posts tomorrow, I want to write about server-side rendering with Rum, and about working with Carry

metametadata16:06:26

of course, it would awesome! but yeah, let's not hurry with devcards issue yet )

metametadata16:06:56

I have a list of things I'd like to be implemented, e.g. debugger UI enhancements

kauko16:06:13

would it make sense to have this list in github or something?

metametadata16:06:18

right, but there are also a lot of stupid/esoteric items )

metametadata16:06:39

so I'd rather much would like to hear what people need first )

kauko16:06:50

Also, the reason I keep pushing devcards, is that in my experience Carry is the first.. framework(?) that really works with devcards. And it works out of the box with no hassle! The great thing about this is not that devcards is some best thing ever, but that if a framework does not work with devcards, that's a big big design smell IMHO

kauko16:06:07

so the fact that Carry just works says to me, that there's been some great design decisions

kauko16:06:28

it's another thing if they hold up when (if?) people start using it in anger

kauko16:06:14

I'd like to write a blog post about this topic, because it really hilights the difference of re-frame and carry. I don't like comparisons, but re-frame is the most popular of the frameworks, so you kind of have to compare to it

kauko16:06:21

but re-frame just does not work with devcards

metametadata16:06:28

I see, yep, but carry also is not that devcards-friendly as we'd like it be - you had to rewrite the core function to make it work 😉

kauko16:06:24

Which took like 1 minute

metametadata16:06:54

yeah, but it's only because the "framework" is 10 lines of code lol

metametadata16:06:41

OK, I'll try to play with the Devcards issue and maybe it will also solve REPL "writable" atom issue. Splitting the debugger middleware also seems like a high priority. Please don't hesitate to tell about any other pain points.

metametadata16:06:34

Oh yeah, server-side rendering is a cool feature to be able to implement

kauko16:06:31

(defn debugger-added? [app]
  (-> app
      :model
      deref
      ::debugger
      some?))
I added this to my carry-rum-debugger

kauko16:06:44

should probably be in the reagent one too

metametadata16:06:34

or it can go into a "core" debugger middleware?

kauko16:06:43

Yeah 🙂

metametadata16:06:46

seems like a common function

kauko16:06:30

(defn start-app [app]
  (let [[app-view-model app-view] (carry-rum/connect app view-model/view-model app/view)
        [_ debugger-view] (when (debugger/debugger-added? app) (debugger/connect app))]
    (mount! app-view debugger-view)
    ((:dispatch-signal app) :on-start)
    (assoc app :view-model app-view-model)))
This is what I needed it for

kauko16:06:09

the spec, app, and middleware are defined elsewhere

kauko16:06:42

with a production profile I don't add the debugger, but I need to be able to use the same start-app function

kauko16:06:55

which is why I have to have the debugger check 🙂

metametadata16:06:25

but what about the final JS file size? wouldn't it be smaller without carry-debugger added?

metametadata16:06:56

I guess with your current approach all the debugger code will also be included in the prod build

kauko16:06:05

oh, good point

kauko16:06:57

maybe I wasn't that smart after all! lol

metametadata16:06:06

so maybe if you reconfigure the bootstrap code you won't need debugger-added? anymore

kauko16:06:37

The thing is, the connect call returns the react component

kauko16:06:13

Right now the entry function, that is the first thing called despite which env we're using, only gets a spec

kauko16:06:37

if the debugger is connected in the dev env code, it would have to take the app and some component

kauko16:06:40

and the component can be nil

kauko16:06:07

which isn't terrible, but it's kind of.. not pretty 😄

kauko16:06:58

Anyway, I'm off for the evening. If there's anything I can help you with, please let me know, I'm anxious to help 🙂 Tomorrow I'll probably start working on the blog series (which will hopefully bring more people to Carry/Rum 😄 ), and I could see about publishing the carry-rum package (which is like, 15 lines long lol) during the weekend

metametadata16:06:32

thank you 🙂

kauko16:06:34

I'll also keep working on this demo app of mine, in hopes of finding more warts from Rum/Carry 🙂

kauko16:06:10

oh actually, I had a question. The way Carry handles async things seems really powerful, (signal comes in, send a :loading action, start AJAX call and :on-complete send another signal), but what if I have a signal where I want to send multiple actions?

kauko16:06:19

Is that something that is supported, or even should be supported?

metametadata16:06:56

like multiple parallel actions?

metametadata16:06:42

maybe I got the question wrong, action just changes the model so it canNOT be parallel )

metametadata16:06:09

you can dispatch-action in a sequence easily from the same signal

kauko16:06:01

Yeah that's what I thought, the only problem I see there is that in theory if you have a signal that triggers 100 actions, stuff will be re-rendered 100 times 😄

kauko16:06:08

But I guess that's not really Carry's concern lol

metametadata16:06:05

yep, it's more of a problem of UI layer which can debounce model updates or redraw strictly 60 fps

metametadata16:06:07

actually latest version of Reagent seems to add some optimizations - reaction updates are batched and UI is redrawn not immediately after a reaction update

kauko16:06:37

you should see the light and start using Rum too 😉

metametadata16:06:25

hah yeah maybe

kauko17:06:05

https://github.com/Day8/re-frame/wiki/Dynamic-Subscriptions This is a problem that Carry doesn't have, right?

kauko17:06:51

https://github.com/Day8/re-frame/wiki/Effectful-Event-Handlers This one kind of applies, but the pain is not felt with Carry so much because of the split between the controller and the reconciler

metametadata17:06:07

Carry doesn't have subscriptions, but in carry-reagent you have something like them in the form of a view model

kauko17:06:18

I'm looking at re-frame's wiki etc, because it's used widely so they've seen how it holds up

metametadata17:06:03

"dynamic subscriptions" seem to be just reaction macro calls in a view-model

metametadata17:06:27

yes, it looks to me that effectful handlers solve the similar problem I solved by separating signals and actions

martinklepsch20:06:15

Hi! Really liking some of the ideas in carry, happy to be able to listen in here occasionally

metametadata20:06:49

@martinklepsch Hi! Welcome and thanks!

metametadata23:06:47

@kauko I think both Devcards and REPL issues can be fixed by introducing atom-sync middleware which makes the specified atom bidirectionally sync itself with the app model. I made a Devcards example, seems like history and hot reloads work on changing the spec and UI code: https://github.com/metametadata/carry/blob/d0efd9a11aff0a6967c345ddebb12f05b6a410c2/examples/counter-devcards/src/app/core.cljs#L72 This way we won't have to touch core Carry API at all.