untangled

NEW CHANNEL: #fulcro
tony.kay 2017-06-19T04:43:05.617387Z

@wilkerlucio The new combo lib is what I can (and plan to) maintain. It has the desired version bumps, but does have some porting things you’ll have to do. I’ve left NAVIS and they have not formally given me permission to do use their github accounts to maintain Untangled. The new “official” version is at my github account: http://www.github.com/awkay/untangled There is a 1.0.0-SNAPSHOT on clojars. (Untangled spec has a 1.0.0-alpha4-SNAPSHOT as well)

tony.kay 2017-06-19T04:43:18.618580Z

though they do not, themselves, plan to do anything with it. I’m working with them to either get them to remove the now-stale resources from the net, or to get permission to take over the untangled-web org…the latter, of course, is my preference.

wilkerlucio 2017-06-20T01:53:25.779862Z

tony.kay: thanks Tony, hopefully this will be resolved soon 🙂

tony.kay 2017-06-20T01:54:25.787564Z

me too

tony.kay 2017-06-19T04:48:31.647535Z

@bbktsk So, client headers. You’ll have to program that into your own networking implementation on the client side. The model is primarily aimed at situations where you log in (and can therefore use cookies). As far as where to store that info. Up to you. If it is just data, then you could put it in the app database if you want. As long as it is serializable data you won’t hurt anything (support viewer needs to be able to serialize the app database).

tony.kay 2017-06-19T04:52:26.669338Z

BTW: I’m going to be on the road (as in car road trip) for the next couple of weeks. I plan to be working on Untangled and other things during this time, but I may be in BFE without inet.

claudiu 2017-06-19T06:14:14.175556Z

@tony.kay will om-css also be moving to a different repo ?

claudiu 2017-06-19T06:15:35.185911Z

also is there a roadmap for untangled, so that we can fallow and maybe pitch in ? 🙂

2017-06-19T14:41:27.481085Z

@tony.kay thank you!

2017-06-19T14:43:26.528330Z

I may have spoken too soon when I said it is working, my problem is back. I was trying to simplify it, with only a partial success. I have a working code that behaves strangely (imho, maybe I’m just doing something wrong), but it is different “strange” from what my real code does. Anyway, here’s the gist:

2017-06-19T14:57:50.878216Z

Short summary: Three components, People, Person (with ::id, ::name and ::car) and Car (with ::type and ::color). Relation between Person and Car is 1:1 . The fake remote api is setup with setTimeout to return response after half a sec and has two endpoints: initial load returns two persons, however, with ::car of each set to nil. Second endpoint returns Car for specified Person. The idea is “Load all persons first, then, on demand, load car individually when requested”. There’s a mutation inital-load (no problem here) and load-car that calls (df/load-field-action state Person [::person-by-id id] ::car :remote :remote :marker false :refresh [::people]). The latter one is invoked by buttons on individual Persons . This is where strangeness begins: first, if I click the button, the request is sent to the fake remote api, then immediately render is called but with empty list of people. Then remote api returns response, render is called again, this time with correct props. In effect, I click the “load car for this person” button, list of persons disappears and is replaced by “no data loaded” placeholder, then, after half a sec, it reappears with correct data, including newly loaded car. Second odd thing is that if I do not include the :refresh key in df/load-field-action, only the first render (with empty list) happens and no render is executed after fake api returns data.

2017-06-19T14:59:01.907285Z

I guess that the main question here is: Why is render first called with empty data?

2017-06-19T15:19:41.415745Z

Oh, and an important note: the above behaviour manifests in Chrome and Firefox. Safari, on the other hand, works as expected (render is called only once, after remote request finishes and with correct data). All on current macOS.

tony.kay 2017-06-19T18:50:40.018959Z

@bbktsk loads replace the data with a load marker. See the docs. You can turn that off with an option to the load call

tony.kay 2017-06-19T18:52:22.055700Z

@claudiu Depends on if I get control of the untangled-web group on github. As far as roadmap: I have not written a formal one yet. If there’s something you’d like to see appear, that is probably the best thing to try to contribute 🙂

tony.kay 2017-06-19T18:53:05.070749Z

sorry @bbktsk didn’t see your comment on load marker…was reading too quickly

tony.kay 2017-06-19T18:54:48.107420Z

so, render is called on any mutation to display local optimistic updates. Render getting an empty list of ppl means the query found no people

tony.kay 2017-06-19T18:54:59.111342Z

which means something wiped them in your db

2017-06-19T18:56:54.153037Z

@tony.kay It seems so, but on the next render (after remote returns) the data are back 😯

tony.kay 2017-06-19T18:58:00.176498Z

have you tried putting in a large enough delay that you can look at the db in the in-between time?

2017-06-19T18:58:15.181539Z

Also, why there’s no render after remote returns unless I specify :refresh […] ? df/load-field-action should refresh target component automatically, right?

2017-06-19T18:58:20.183574Z

good idea. will try now.

tony.kay 2017-06-19T18:59:17.203438Z

so, refresh happens on the component that triggered the mutation. Nothing is automatic beyond that. Om has no idea what data you’re mutating, so it assumes the sub-tree only. The follow-on reads (e.g. refresh) are needed if you update something outside of that tree.

tony.kay 2017-06-19T18:59:42.212325Z

My guess is you’re triggering the mutation far into the tree?

tony.kay 2017-06-19T19:03:07.286647Z

I’m looking at your gist…just a min

tony.kay 2017-06-19T19:04:22.312482Z

OK, so I see several things.

tony.kay 2017-06-19T19:04:40.318767Z

1. Does more than one person refer to the same type of car?

tony.kay 2017-06-19T19:05:04.326653Z

If so, you need to load the car from the list level, or do the follow-on read

tony.kay 2017-06-19T19:05:39.338446Z

2. You do not need to write an initial-load mutation. Just use load.

tony.kay 2017-06-19T19:05:59.345323Z

3. You do not need to write a load-car mutation, just use load-field

tony.kay 2017-06-19T19:06:50.361660Z

But note: load-field MUST be done in the context of something that joins it as a field in the query, which will necessitate a follow-on read if that thing might be visible elsewhere in the tree

tony.kay 2017-06-19T19:07:35.376713Z

e.g. it goes in Person…`(load-field this ::car {:refresh [::people]})`

2017-06-19T19:07:54.382763Z

Tried to longer delay. Initial load, state is good. Hit “load car”, people disappeared, checked state, it is still good (original data + some untangled stuff). It seems the state atom is not wiped.

2017-06-19T19:08:24.393060Z

1. Nope, strictly one to one.

2017-06-19T19:09:20.411494Z

2. + 3. yup, tried both (or at least for 3.), same result.

tony.kay 2017-06-19T19:10:32.435481Z

oh…line 55

tony.kay 2017-06-19T19:10:34.436273Z

don’t do that

tony.kay 2017-06-19T19:11:27.453405Z

or at least, line 52 and 55 are not compatible

tony.kay 2017-06-19T19:11:37.456570Z

one says the data is relative to the component, the other says the data lives at root

tony.kay 2017-06-19T19:12:11.467076Z

Where is your root, BTW?

2017-06-19T19:13:45.497529Z

Root component? It’s People. It’s missing ui/react-key, but I fixed that already.

tony.kay 2017-06-19T19:14:03.503322Z

Ah, so root is special. It doesn’t get an Ident, because it IS root

tony.kay 2017-06-19T19:14:21.508955Z

if you give it one you get an extra edge in your graph that can just be confusing

tony.kay 2017-06-19T19:14:43.516074Z

And the query of the root component is against the root of the db automatically, so your query should not use a link query

tony.kay 2017-06-19T19:15:02.521959Z

is should join on the {::people (om/get-query People)}

tony.kay 2017-06-19T19:18:06.582741Z

I don’t necessarily think that should fix your problem, though 😕

tony.kay 2017-06-19T19:19:18.606257Z

I think line 113 might be a problem

tony.kay 2017-06-19T19:20:20.626249Z

load-field writes a query of the form: [{[:person-by-id id] [::id {::car [::type ::color]}]}]

tony.kay 2017-06-19T19:20:34.630315Z

your response has an extra level in it

tony.kay 2017-06-19T19:21:33.649501Z

the response should be:

{ [:person-by-id id]  { ::id n  ::car { ::type x ::color blue } } }

2017-06-19T19:21:38.651193Z

aaaaaand it works now 😎

2017-06-19T19:22:04.659535Z

113 is ok , the extra ::person-by-id is just value for condp a few lines above.

tony.kay 2017-06-19T19:22:17.663715Z

Oh, I see

tony.kay 2017-06-19T19:22:18.664254Z

mis-read

tony.kay 2017-06-19T19:22:36.670024Z

so the link query was breaking it???

tony.kay 2017-06-19T19:25:31.726587Z

oh…probably the ident was causing a problem with added indirection

2017-06-19T19:29:25.803964Z

Yes, just tried all combinations, it started to work only after I removed ident on People and changed the query to relative.

tony.kay 2017-06-19T19:30:23.823488Z

Yeah, an ident on root means you want it to be normalized (put in it’s own table)…but its root

2017-06-19T19:30:41.829297Z

Hmmm. I wonder if that’s what I am doing wrong in my real app.

tony.kay 2017-06-19T19:31:27.844829Z

I’ve never tried it but I’d suspect it would put the initial tables into a non-root node, making your tables not work like tables.

tony.kay 2017-06-19T19:31:47.851548Z

well, we know one thing: it breaks &^$

tony.kay 2017-06-19T19:32:20.862539Z

perhaps worth a warning note in the Dev Guide

2017-06-19T19:37:08.955262Z

Just a final quick question: I assume that if suddenly :om.next/tables in my app state is an empty set, something went wrong, right?

tony.kay 2017-06-19T19:44:01.086294Z

No, I think that is an old om next thing that isn’t actually used

tony.kay 2017-06-19T19:44:48.101121Z

I think D.N. originally thought it was going to be needed, and wrote some code to make it, but as far as I can tell it isn’t used nor does it maintain proper track of the tables.

tony.kay 2017-06-19T19:45:07.107056Z

I expect it will disappear as they clean up the code for a 1.0 release there

tony.kay 2017-06-19T19:46:41.137315Z

the only place you can even find ref to it is in the tree->db function (which normalizes things). Nothing in Om Next ever tries to read it.

tony.kay 2017-06-19T19:46:50.140175Z

(except in two old tests)

tony.kay 2017-06-19T19:48:49.178396Z

the merge behavior will overwrite it every time a normalization happens (which is after every network interaction)

2017-06-19T20:00:54.417840Z

Thank you. There goes my last clue 😎 Anyway, thanks a lot for you help, again 😎 Although I’m having some problems, I really like Untangled so far.

tony.kay 2017-06-19T20:05:40.513407Z

You’re welcome. I’m glad you’re liking it.

tony.kay 2017-06-19T20:07:19.544868Z

I like how most of the problems end up being something really pretty simple. If you get the ident, query, and initial state right, you’ve got most of it…and those are so simple once you get it.

tony.kay 2017-06-19T20:08:07.560662Z

then you have mutations: pure functions from one state to another. It’s unfortunate you need REST…because when you don’t it just stays nice and simple 😉

tony.kay 2017-06-19T20:08:45.573288Z

not that REST is all that hard to add, as you’re seeing

2017-06-19T20:11:52.634725Z

Yup. It’s just a few days and my project is shaping pretty well. Now I just need to figure out why the reconciler throws that exception 😎

tony.kay 2017-06-19T20:14:29.684408Z

@bbktsk So, there is a chance you’ve found a bug. Do you have a full stack trace?

tony.kay 2017-06-19T20:16:24.721603Z

I assume you’re trying to load extra stuff on one thing (perhaps in a list of things), and after the first load you try another one, and that’s when you get the error?

2017-06-19T20:17:21.739768Z

@tony.kay That’s almost exactly what I am doing. One stacktrace coming up…

2017-06-19T20:18:34.762987Z

core.cljs:5487 Uncaught Error: Index out of bounds
    at Object.cljs$core$build_subvec [as build_subvec] (core.cljs:5487)
    at Function.cljs.core.subvec.cljs$core$IFn$_invoke$arity$3 (core.cljs:5499)
    at Function.cljs.core.subvec.cljs$core$IFn$_invoke$arity$2 (core.cljs:5497)
    at cljs$core$subvec (core.cljs:5490)
    at <http://om.next.Reconciler.om|om.next.Reconciler.om>$next$protocols$IReconciler$reconcile_BANG_$arity$2 (next.cljc?rel=1497896997432:2534)
    at <http://om.next.Reconciler.om|om.next.Reconciler.om>$next$protocols$IReconciler$reconcile_BANG_$arity$1 (next.cljc?rel=1497896997432:2485)
    at Function.om.next.protocols.reconcile_BANG_.cljs$core$IFn$_invoke$arity$1 (protocols.cljc?rel=1497896971054:20)
    at om$next$protocols$reconcile_BANG_ (protocols.cljc?rel=1497896971054:11)
    at next.cljc?rel=1497896997432:1470

tony.kay 2017-06-19T20:20:44.806116Z

so, couple things to check:

2017-06-19T20:20:51.808508Z

Loading data using (df/load-field-action state Bean [::bean-by-id ident] ::bean-attrs :remote :remote :marker false). There is some massaging of data being done in send, but the result is just a new value for a field.

tony.kay 2017-06-19T20:21:59.831309Z

So, in your UI, when you pick apart props and pass them to children, are there any cases where you’re not being careful to pass (unmodified) the props for a child to that child?

tony.kay 2017-06-19T20:23:23.859506Z

or where you’re rendering something outside of where it is queried?

tony.kay 2017-06-19T20:26:46.928441Z

Here’s why I ask: When Om does a UI refresh, it tries to do a targeted one (so that only the subtree causes overhead). The place where that exception is thrown: it is using the path (in the tree) of components, which is added to the data tree before being handed in to render. E.g. the database is queried, and a tree of data is returned. The UI query and this tree are then used to annotate that data tree with metadata at each component data node. The two must match, or strange things happen

tony.kay 2017-06-19T20:27:39.946255Z

Then the tree is either passed to root (and picked apart as you render) or the targeted component for partial refresh is given the partial result of the focused query

tony.kay 2017-06-19T20:28:04.954307Z

I’m not doing a great job of explaining it 😕

2017-06-19T20:30:17.999128Z

By “not being careful” do you mean “is it possible that some component is receiving data it is not declaring in its query”?

tony.kay 2017-06-19T20:30:51.010966Z

Transact happens ---> Om pulls the query, and eliminates all the bits that don’t have to do with the subtree that triggred the transact (this is called focusing the query) ---> Om runs parser on query and pulls data from db ---> Om runs path-meta to add metadata to this data

tony.kay 2017-06-19T20:31:14.018687Z

Yes, any mismatch on stateful components is an error

tony.kay 2017-06-19T20:31:43.028865Z

if something queries for data, it better be composed into parent query, and the props must be picked apart in a similar fashion from the props it receives

2017-06-19T20:32:52.052194Z

I’m 90% sure that with every subcomponent that has something like {:subc (om/query Subc)} in component's query, I just do (subc (:subc props))` somewhere in render. But I’ll check that.

tony.kay 2017-06-19T20:33:23.062706Z

Yep. Anything else that flows through the data tree must use om/computed

tony.kay 2017-06-19T20:34:19.081756Z

I’ve had one recent case of my own (that I have yet to track down) where I think the indexer got confused. Moving the transaction to the parent worked as a workaround, but you cannot do that with load-field

tony.kay 2017-06-19T20:35:10.098265Z

It wasn’t the same error, though.

2017-06-19T22:11:35.733178Z

@tony.kay Now I am 99% sure I am passing only what’s requested to each component. Although I have found interesting thing. I have hierarchy Doman -> Bean -> Attrs . By default, y˘ou see just a list of domain names. To see Domain’s Beans you have to click “show details”. Same for Bean and its Attrs. “show details” is a local mutation that takes ident and does just (swap! state update-in (conj ident :ui/open?) not (Domain and Bean have :ui/open? in their query and data). Also, when Domain is opened, a remote mutation is started (together with the “show details” one), to load extra data for Beans’s Attrs using df/load-field-action. Or that’s the plan, at least. When I removed the remote mutation (so that only local “show detail” mutation that toggles :ui/open? remains), everything worked just fine. I could open (and close) the hierarchy all the way down, no problem. Then I modified code so entire hierarchy is displayed all the time, regardless of :ui/open? on any level. Also, I added button to just start the remote mutation on individual Beans. And again, everything works. I click the “load data” button on each Domain and bam, data show up in domain’s Attrs without problem. So far, if I do only “show detail” mutation or only “load attr data for domain”, it works. But, if I load data to Beans in two different Domains and then do “show detail” on one Domain and then “show detail” on one of its Beans, I get that Index out of bounds exception 😮 . Most other combination do work: load data to one Bean in one Domain, then “show detail” everywhere, ok. load data to all Beans, “show detail” only on “Bean” (instead of going from top level Domain), works. And so on. The sequence to trigger the error is pretty specific, unfortunately exactly matches what I need to do. I guess the way to go now is to look into state and db->tree dumps “before” and “after” and look for something odd?

tony.kay 2017-06-19T22:14:40.773305Z

Hm.

tony.kay 2017-06-19T22:23:46.886759Z

It won’t hurt to verify that you graph is sane.

tony.kay 2017-06-19T22:24:05.890607Z

but it is very odd that you’d be able to do it most ways except one

tony.kay 2017-06-19T22:25:58.912701Z

I kind of suspect it has to do with the incremental render in Om (which prevents more costly from-root rendering)

tony.kay 2017-06-19T22:27:07.926393Z

The error you’re getting is in that exact algorithm…it walks from the component towards the root updating props

tony.kay 2017-06-19T22:30:27.966089Z

If I was sure the code was essentially right, I’d probably set the project up with Om in checkouts so I could debug the reconciler and see what’s going on.

tony.kay 2017-06-19T22:30:53.971486Z

well, for that matter, if you have devtools installed you can set breakpoints in Chrome and look at values

tony.kay 2017-06-19T22:33:10.998857Z

I’ll try to throw together a minimum case of what you just described in a devcard and see if I can reproduce it.

2017-06-19T22:39:24.069545Z

Thanks, that’d be great. I just compared state and (om/db-&gt;tree (om/get-query Root) @state @state) just before and after the exception, and there is no problem, the only difference is that ui/open? changes from false to true, as expected. btw, is that the right way to use om/db-&gt;tree? Docs says that the second arg is “some data in the default database format” and I am not quite sure what “some data” means here.

tony.kay 2017-06-19T22:39:42.072870Z

yes

tony.kay 2017-06-19T22:40:26.080995Z

it is recursive, so one of them has to be the root node (where the tables are at), while the other could be some data (which in turn contains idents)

tony.kay 2017-06-19T22:40:35.082723Z

when starting from root, they are the same

tony.kay 2017-06-19T22:41:03.087952Z

if you were starting, say, from Domain query, you’d give the value of a Domain as the data

tony.kay 2017-06-19T22:41:22.091648Z

but you’d still give the root for ident lookups

2017-06-19T22:49:33.181977Z

Aaaah! Now it makes sense! Thanks.

tony.kay 2017-06-19T22:57:36.270548Z

@bbktsk Well, I think I have something similar, and it works fine for me

tony.kay 2017-06-19T23:01:22.314147Z

I can throw it up on a branch and you can have a look

tony.kay 2017-06-19T23:02:48.330129Z

You can clone the repo, and switch to the load-sample branch

tony.kay 2017-06-19T23:03:08.333811Z

Then run figwheel with the -Ddemos build

tony.kay 2017-06-19T23:03:37.339263Z

Then run a standard REPL and run (run-demo-server)

tony.kay 2017-06-19T23:03:56.342653Z

Then use http://localhost:8081/demos.html#!/cards.load_samples_cards

tony.kay 2017-06-19T23:04:20.347026Z

ignore the comments. They’re for the unmodified code…which this is not

2017-06-19T23:24:48.552478Z

I did some debugging and I know what the problem is, although I do not know who to blame 😎

2017-06-19T23:28:46.591377Z

There’s a Bean component inside a Domain component. The code that throws exception is here, from next.cljc, function reconcile!, starting at line 2531:

(when-let [update-path (path c)]
  (loop [p (parent c)]
    (when (some? p)
      (let [update-path' (subvec update-path (count (path p)))]

2017-06-19T23:29:48.600857Z

When this executes, c is the Bean component, its path is [[:wmcng.beans/domain-by-id "java.nio"] :wmcng.beans/domain-beans 0]

2017-06-19T23:30:29.608018Z

The parent is Domain and its path is [:root-router :current-route :wmcng.beans/domains 1]

2017-06-19T23:31:38.619327Z

count of Domain’s path is 4, however, Bean’s path has only three elements, so it fails.

2017-06-19T23:32:17.625444Z

The code apparently assumes that parent’s path is a prefix of child’s path?

2017-06-19T23:32:44.629925Z

So, is this assumption valid and I messed up? Or is it not valid and I can blame someone else? 😎

tony.kay 2017-06-19T23:33:12.634078Z

um. try this: There is an option on untangled-client which is passed through to om

tony.kay 2017-06-19T23:33:42.638905Z

:path-opt I think it is…try setting it to false

2017-06-19T23:39:07.689530Z

No change. Are you sure about the name? There’s no mention of path-opt in entire om repo.

tony.kay 2017-06-19T23:39:24.692042Z

hold on

tony.kay 2017-06-19T23:39:38.694370Z

no hypen

tony.kay 2017-06-19T23:39:39.694575Z

pathopt

tony.kay 2017-06-19T23:40:53.705825Z

If that fixes it, I may just set it to false by default in Untangled…I’m not sure the regular Om community even knows about it. Early on it was meant to be a way to optimize re-renders…but it is possible it fell through the cracks

tony.kay 2017-06-19T23:41:34.711676Z

It has certain requirements that Untangled fulfills for you, so it defaults to off for regular Om Next

2017-06-19T23:54:56.831595Z

@tony.kay Nope, still the same error (same place & reason)

tony.kay 2017-06-19T23:55:13.834009Z

hm. you’re using 1.0.0 snapshot of Untangled, right?

tony.kay 2017-06-19T23:59:18.871102Z

well, it certainly looks like a bug in Om Next to me.

tony.kay 2017-06-19T23:59:51.876092Z

the assumption that an om-path will be longer in a child, when a child’s path can use an ident to shortcut, seems like a problem to me