@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
but since carry's model atom is actually read-only (EntangledReference), I don't think this can work
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
Hey @kauko! The idea of Carry API was to force a user to only change model through the signals
is the signal log available from the REPL?
so that's how state can be changed from the REPL:
((:dispatch-signal app) some-model-changing-signal)
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
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"?
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 🙂
TBH I don't think the history thing is that important, data inspection is much more important
but history would be nice obviously
I don't quite imagine what you mean but I'd like to see how it works 🙂
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..
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
exactly
Devcards has some Reagent specific macros, and I was thinking of adding some for Carry too
the macro could maybe set up the debugger
hmm
or.. add a :carry-debugger
option to devcards. I'll have to talk to bruce about this at some point
I doubt he'll be happy about bloating the devcards just to support some new frontend framework )
Yeah
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.
Wouldn't that signal still need to be defined in the app somewhere?
Actually, how does the debugger implement time travel? Or does it?
Does it only allow you to go back to the initial model, and then replay all the actions that took you there?
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
yes. that signal would need to be additionally defined
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
(so signals are not really replayed - only the action dispatched from signals)
I agree, saving a log in production and recording it to debug real errors seems like a nice idea
but I'd like to not make it a part of Carry core, an optional middleware would be better
I even think the extracted carry-debugger-model
(name is TBD) middleware will suffice to make it work
carry-signal-log
? 🙂
I dunno ) I'd like it to be something about a debugger as it will also add all the debugger's signal handlers
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 😛
I agree, but I'd like to not have 2 separate middleware with a lot of duplicated code
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?
well, and one can actually imagine it to be a "debugging", just an offline one )
I guess you understand me correctly, let me write it down again so that we are on the same page..
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.
for a moment I thought you wanted to also implement signal-log
middleware in addition to debugger-core
one
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))
?
well. yeah
because the way you send logs to server is specific to your app
maybe you send them to Sentry or maybe your own server
@metametadata: haha, actually Carry worked with devcards straight out of the box 🙂
Just had to do this
So basically we just need something like carry/app
, but it needs to take an atom as the initial model
so devcards can use the same atom as its data atom
Oh wow, even the history seems to work!
@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
if I have some faulty data in my model, I'd like to be able to edit it
would it help if app
returned :-writable-model
or smt. in addition to :model
and :dispatch-signal
?
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
or one can even create a utility middleware/function which exposes a "writable" app model for debugging
I'll try to play with devcards today and see if we can make it work without changing the core API much
This would be enough to make carry work with devcards
Devcards would call -build-app
directly
but honestly, all it needs is a way to provide the initial model as an atom, not a value
then it works
The writable atom / REPL thing I mentioned is not really related to devcards, it's another concern 🙂
ah I see
But actually the :reset
signal is a good point
I have a feeling that we can make it work without an additional -build-app
function
Great! How?
we can make devcard's state
atom a "proxy" which calls ((:dispatch-signal app) [:reset ...])
or maybe not )
I'll try to do that
so that user will only have to call an utility helper function to bind Carry and Devcards together
Sounds interesting! Is it possible to add a "generic" [:reset ..]
signal? One that is always available in a carry app?
it can be easily added via a middleware
Yeah, I'm just wondering whether it should be added automatically
ah, I'd rather force user to do it explicitly
Yeah that probably makes sense, I just brought this up because I know I would've come across that need very soon
and if it's a need that 95% of devs have, it could make sense to just have it available
and look as "familiar" as possible
but I mean, it's your call. 🙂 Just bouncing ideas
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 ))
another possible scenario is that most devs would like to dispatch actions instead of editing a model by hand
and for some time there was a hidden :-dispatch-action
available for this purpose
but then I decided to delete it to keep API smaller and bc it's possible to simulate it using an additional signal
I wonder if there should be something like a carry.repl
middleware
that adds these sorts of things
yep, maybe
let's see how it goes
and add it when it feels like it's really commonly useful
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 🙂
So the easier I can see the data, and change the data through the REPL, the happier I will be 😛
some solutions hide it too much
OK, cool example, it's the kind of scenario which doesn't include dispatching actions
Yeah, just what data is in the model 🙂
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
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
of course, it would awesome! but yeah, let's not hurry with devcards issue yet )
I have a list of things I'd like to be implemented, e.g. debugger UI enhancements
would it make sense to have this list in github or something?
right, but there are also a lot of stupid/esoteric items )
so I'd rather much would like to hear what people need first )
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
so the fact that Carry just works says to me, that there's been some great design decisions
it's another thing if they hold up when (if?) people start using it in anger
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
but re-frame just does not work with devcards
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 😉
Which took like 1 minute
😄
yeah, but it's only because the "framework" is 10 lines of code lol
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.
Oh yeah, server-side rendering is a cool feature to be able to implement
(defn debugger-added? [app]
(-> app
:model
deref
::debugger
some?))
I added this to my carry-rum-debuggershould probably be in the reagent one too
or it can go into a "core" debugger middleware?
Yeah 🙂
seems like a common function
OK
yup
(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 forthe spec, app, and middleware are defined elsewhere
with a production profile I don't add the debugger, but I need to be able to use the same start-app
function
which is why I have to have the debugger check 🙂
I see
OK!
but what about the final JS file size? wouldn't it be smaller without carry-debugger added?
I guess with your current approach all the debugger code will also be included in the prod build
oh, good point
maybe I wasn't that smart after all! :lol:
so maybe if you reconfigure the bootstrap code you won't need debugger-added?
anymore
lol
The thing is, the connect
call returns the react component
Right now the entry function, that is the first thing called despite which env we're using, only gets a spec
if the debugger is connected in the dev env code, it would have to take the app and some component
and the component can be nil
which isn't terrible, but it's kind of.. not pretty 😄
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
awesome!
thank you 🙂
I'll also keep working on this demo app of mine, in hopes of finding more warts from Rum/Carry 🙂
❤️
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?
Is that something that is supported, or even should be supported?
like multiple parallel actions?
yeah
hm
maybe I got the question wrong, action just changes the model so it canNOT be parallel )
you can dispatch-action
in a sequence easily from the same signal
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 😄
But I guess that's not really Carry's concern lol
yep, it's more of a problem of UI layer which can debounce model updates or redraw strictly 60 fps
actually latest version of Reagent seems to add some optimizations - reaction updates are batched and UI is redrawn not immediately after a reaction update
you should see the light and start using Rum too 😉
hah yeah maybe
https://github.com/Day8/re-frame/wiki/Dynamic-Subscriptions This is a problem that Carry doesn't have, right?
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
Carry doesn't have subscriptions, but in carry-reagent you have something like them in the form of a view model
I'm looking at re-frame's wiki etc, because it's used widely so they've seen how it holds up
"dynamic subscriptions" seem to be just reaction macro calls in a view-model
yes, it looks to me that effectful handlers solve the similar problem I solved by separating signals and actions
Hi! Really liking some of the ideas in carry, happy to be able to listen in here occasionally
@martinklepsch Hi! Welcome and thanks!
@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.