Using streams with recursive and/or deeply nested schemas

You may get a better reply but briefly, the purpose of that first update/2 clause is to receive the updated entry from send_update/2. In the example the main LiveView receives an updated entry over PubSub and dispatches it to the relevant LiveComponent.

If you are not using that send_update/2 functionality then that update clause won’t be called because there is nothing to trigger a re-render of the components at all (they are all rendered as nested streams, once).

I’m wondering - how do you plan to render the “informational content” which you are storing separately?

First time I asked I read your response to mean that it’s exactly that first clause that’s the key but that makes the difference between only the parent, only the children or both being updated, simply because that what I asked about - what in the example makes that difference and that where you pointed. But it can only make that difference if some of the time it gets called and does something other than the default. I understood the load_children function to be a (PubSub-less) simulation of of a chance that impacts the children without the parent and I was busy trying to figure out how the reverse would be made possible - the parent getting updated without impacting the children what was the primary concern at the time.

I’ve been compiling a chart complete example of my own to answer that question if I’m able to pull it off in the first place. That’s exactly why I’ve been engaging to intently with the example and how to does its bussiness and how ran into the befuddling fact that the code I was as lead to believe is the key to the solution.

Not yet being sure how the tools actually work means it might yet find what doesn’t to do impossible.

However, principle is this. (Obviously) for the first (mount)? It will be a full normal render. The tree component will build the structure into which the content is rendered, still fundamentally recursive following the structure element’s recursion. At each level of recursion the children are drawn in from the linear list and rendered into place where they end up getting placed as inner content slots within the parent. Nothing about that is unusual or outside the norm. The only fact that clashes with the assumptions LiveView makes is that the resulting dom is recursively nested rather an a repetition of non-overlapping dom elements.

The differences only come into play when updates are getting involved. I’m still trying to identify and leverage the exact opportunities available to this with, but the general principle is to implement a full set of tree operations at the LiveComponent level. I’m hoping to do most of that in the single whole-tree LiveComponent but it will most likely require that a per-node LiveComponent cooperates in a prescribed manner, much like implementing a behaviour. This would all be towards enabling tree operations to optimise by either preventing or (selectively) absorbing unnecessary changes getting picked up by diffing.

The complete set of tree operations have been largely known theoretically since Knuth’s time, but to make them real enough to program one only needs to look at the things we can do with file systems and their directory structures. It’s a great analogy of a tree and how tree information (directory structure) even though directories are often also special files, are managed differently from the files. The difficulty with the DOM is that in filesystem terms the contents of the files and the directory structure is all in one tree. Our challenge is to create a way to surgically manipulate the DOM remotely anyway.

It was in the context of my attempts to show this working rather than just talking about it that I got caught up in the cognitive dissonance of how Steffen’s example actually works. But that’s now waiting for an answer (sorry, but yours didn’t help me at all) so am forced to describe rather than show.

The overarching concept though is based on the premise that even the most complex, contrived and unpredictable ways a tree and its contents can get manipulated by incoming data or by user interaction can be detected as and reduced to a sequence of primitive changes along the lines of

  • saving a new file in a directory
  • saving to an existing file
  • moving a file from one directory to another
  • moving an entire directory from one parent to another
  • renaming a file or directory
  • deleting a file
  • deleting a directory and everything below it
  • change attributes which affect access and visibility rights which impacts what is shown in a listing and the order they appear in
  • Walk through a directory structure from a starting point with a possible depth limit to look for and/or take actions on each file or directory found
  • Work out how much space is used by what optionally summarised or detailed per item.

It is vitally important to note that the tree operations I describe above can be applied on two levels - the big underlying tree or the visual presentation of that tree for one or more users. None of what I am describing here is aimed at manipulating the underlying tree. That part is for you and me to implement however suits our data and our users best. What I am talking about here is to take the things we or our users do with the data and programmatically (largely declaratively) map those actions on what needs to happen to the visually represented trees derived from that data and the changes to it.

In other words, I am not suggesting that we merely detect the require changes in any way similar to what LiveView does with diffing and try to retrofit the deltas to the tree operations. The aim would be to take the actions right there where they are implemented for us or our users to change the underlying tree with and propagate those in tree primitive form (via PubSub) as what the various LiveView component instances currently presenting that data somewhere in a tree for a user must make happen to correctly and efficiently reflect the changes to the displayed DOMs.

If the trees were small enough it would been feasible to keep updating a whole tree at a time which is what diffing ends up doing be default. By limiting the loaded window and taking the bulk of the content (file content) out of the “directory structure” leaving only ids (disk node numbers of files) we’re making the tree small enough to let LiveView do the heavy lifting. What remains is to use our knowledge of the structure of the data, what it will look like in the DOM and what we know is meant to be happening to filter and augment what diff picks up and turn that into the minimal updates that needs to go to wire.

Update: I eventually found where the first update clause gets involved. I expected it to play a role in the “Load Children” test case while it actually plays its part with the “Rename bar.txt” test case. The reason my changes broke the “Load children” case is not related to that clause.

See above.

While I’m adjusting my mental models to accommodate this realisation I’m faced with yet another uncertainty I shall post about soon.

Since you are still waiting I will try to give you some more background.

As mentioned in Steffen’s post, there are two things which trigger the update/2 callback. The first is a re-render from the parent, which will call it with whatever assigns are passed to the <.live_component /> invocation in the parent.

The second is the function send_update/3, which can be used to send some values (assigns) directly to a LiveComponent. It’s conceptually similar to message passing, but it’s not actually message passing because, as you might recall, LiveComponents exist within the same process as the parent. Docs here.

When you invoke send_update(MyLiveComponent, id: "some-id", key: "value") then the component MyLiveComponent with id some-id on the page will receive update(%{key: "value"}, socket).

(I will point out for the record that this really isn’t Phoenix’s most intuitive API.)

In Steffen’s example, this send_update/3 functionality is used to rename an entry, as I think you have since discovered.

There is a third way for a LiveComponent to be re-rendered: receiving an event (like phx-click). But unlike the first two, this does not call update/2. Instead it invokes the handle_event/3 callback, which updates the assigns accordingly (and then the component is rendered).

In the example, it is the event functionality which dynamically loads more children, which is why update/2 is not called in that case. If for some reason you wanted to trigger a reload of the children via, say, PubSub, you could create an update/2 case for that functionality and use send_update/3 to trigger it.

So back to the two update/2 clauses in the example. The first one, as we discussed earlier, pattern matches on an existing socket with an entry already present. What this means, in effect, is that this first clause will only be invoked if the entry has already been added to the LiveComponent. In other words, it will only be invoked after the first render.

Conversely, the second update/2 clause will only be invoked on the first render.

Now it’s very important to understand that there is a trick being used here: streams. Each LiveComponent in the example streams its children, and the child of a stream is only rendered immediately after a stream_* call (like stream/4 or stream_insert/4).

And so that is how we arrive at the behavior you have observed: as it turns out, nothing in Steffen’s example actually calls stream/4 on an entry’s children again after the initial render. And so nothing in this example ever triggers update/2 after the first render, unless you use send_update/3 (the rename operation).

That is why you have not observed the first clause being invoked.

I’ve figure most of what you’re saying out, but this conflicts with what my debug statements showed. It might be because times it went via clause #2 was “technically” first renders of new elements, but when I definitely saw the second clause matching after the initial render.

I’ve been doing some pattern matching of my own to try instrument tree operations as I’ve described, and in the process found a section in the LiveView manual (around send_update I think) that describes conditions whereby the assigns get merged. So I went looking for the additional assigns I make in send_update in the assigns parameter rather than the socket’s assign member where I was looking for it because the :entry assign was there. So there’re definitely some fluidity and non-intuitive conditions going on behind the scenes, but once you find the stuff you’ve been sending across, it looks quite doable to implement the tree primitives I wrote about. I managed to get the first one, add_child (given as a map, adding to a parent given as an id) to work like a charm. In the current implementation I’m essentially using the :children stream to pass the children to be added as “parameters” for the tree operation, but I suspect I’ll eventually change that. However, as it is it became quite useful that the streams get “cleared” after being used on the server. It is making a lot of sense to me now to think of a stream as a virtual construct on which list-like operations are implemented but it only lives in actual (partial) lists for short periods of time.

Anyway, thanks for you help, it does help. I’ve been distracted today generating big enough arbitrary test data of the kind we use in this discussion so I can post that as part of my suggested solution in the hope that it becomes rather obvious when the tree operations are working optimally and when they’re triggering big updates. With small data the difference is too small to notice without deep inspection into hard to reach places.

I gathered as much. The main point he was trying to make in response to how he understood my questions was to show how a parent would be updated without updating the children as well. So the rename bar.txt button and its’s send_update was more or less what the example was about, but the Load Children button which relies on the default mechanisms was what my first attempts at doing some changes of my own broke so horribly. That got me off course, but I think I’m back now.

The first render for each component, yes. When you load the additional children they are rendered for the first time, so they would invoke the second clause (because their sockets do not yet contain an entry).

Update 3:
Since my last post on this thread I’ve invested a great deal of energy to come up with a solution that maps the non-linear domain of indefinitely recursive data onto the (flat and) rectangular domain of browser windows and grids. It’s been quite a journey so far, with really exciting results, but it’s not over yet. The objective is to build something suitable for all three use-cases for this I have in my project while also being of potential use by others with similar use cases. The general idea is to leverage existing LiveView with Streams facilities as much as possible but in a suitably intelligent way which prevents the recursive nature of the data from breaking the bank on memory and/or network bandwidth utilisation.

But then…
Having spent a lot of time building not only the beginnings of this library but also a way to generate non-repetitive test data that is structured in a way that makes it possible to visually confirm that the right data and changes to data is being reflected on the screen while also being able to control the size of the test database for different stages of development and testing, I will soon be at a point where I need to confirm that all the required updates and nothing but the required updates are being sent to the client. Because of the gist @steffend posted I have a rough idea about controlling what detected changes are allowed to go through to the client but I’d need more complete coverage than that. That way only picked up a specific scenario to cull some of the the updates based on a privileged understanding of how the update code moves parameters from the socket to the assigns. I’m going to need a more complete view than that.

Meanwhile, a question:
Where and how can I trace, log or debug what changes LiveView detects and which of those end up going out on the wire?

I’m reasonably comfortable that I’d be able to get a view on how it plays out in the PubSub domain, but I need direction as to how to get visibility of the LiveView changes and traffic. I’d hate to have to resort to WireShark etc for that. @garrison, you mentioned somewhere how you were able to determine that the updates being sent through didn’t meet with your hopes and expectations when using streams and I presume you used the same method to verify that not using streams had the desired effect. Did you use external tools or were you able to do that in the code itself?

I’m after:-
a) a way to see what changes liveview has detected in the assigns when processing an update, and
b) a way to see (probably in the browser’s JS console) what updates had been received at the client.

P.S. I know of the existence of some LiveView dashboard but never explored what it can and can’t do. I’d be deligted if it handsomely covers part b) above.

1 Like

BTW, by far the most interesting and challenging problem I had to solve in this regard was a functional and efficient algorithm that retrieves exactly the data required for one rectangular viewport of the tree being displayed to use in paginating the tree in two dimensions. LiveView, Streams and most of HTML is built for one-dimensional lists (of records) where all the pagination required can be controlled by the limit and offset parameters which gets carried through from the LiveView contreoller via Ecto all the way to the database that implements those directly. None of which applies to hierarchical or recursive data. I’ve not run formal tests but I believe the solution I ended up with has a constant execution time that depends only on the size of the view window, not the size of the underlying database. That would be an improvement on native offset and limit queries which could slow down drmatically when run against large datasets. It’s a relatively complex algorithm involving multiple small read operations which are all contained within the database environment itself rather than going back and forth over the network between the database and web servers.

I’ve also managed to decouple the rendered content from the hierarchy. Simply put, none of the tree nodes that end up in the assigns or stream has any (pre)loaded child nodes. Each is rendered as their own HTML never overlapping the HTML of another node. Visually and behaviourally the content appears nested recursively as it should be, but there’s no actual nesting involved, massively reducing the complications of diffing content. Since the pagination code can then accurately track exactly which nodes should be added and removed from the client to render the new page as a change from the current and the order of nodes in DOM memory no longers matters (their IDs uniquely dictate where in the window their HTML is positioned) it theoretically opens the door to using streams once again without needing to complications associated with nested streams.

I’m pleased with what I’ve been able to put together so far. Fingers crosses it all works out as planned.

When you call assign(socket, :key, value) LiveView diffs the value by comparing it to the previous value (essesntially value == prev_assigns[:key]). If the value has changed LiveView adds the key to the assigns.__changed__ map. You can look inside the __changed__ map or call Phoenix.Component.changed?/2 to see if a key has changed after an assign. You could also probably inspect it on render to see all the changes in that batch - I think that would work.

I don’t know if there are any fancier tools. I have seen some hype about this LiveDebugger tool - I haven’t tried it and I don’t know exactly what it does, but it might be relevant.

You can inspect the messages sent over the socket in your browser devtools. It’s going to be different per browser but generally you can click on the socket in the network tab and see the messages and their sizes going over the wire.

Just to clarify a bit (it’s been a while), the problem I had with streams was that they made it harder to build the UIs I want - not performance problems. Streams do solve the performance problem caused by large comprehensions in a LiveView, they just make it really hard to build a complex/reactive UI because they constrain updates to a single node and don’t give access to the existing data. I have continued to have better results with LiveComponents.

There are cases where stream diffs will be larger than LiveComponent diffs, though. The stream has to send the whole object back over the wire while the LC can diff each attribute since it holds the previous attributes in memory. Whether this matters depends on how many attributes you have per object and how large they are. And it is of course a tradeoff between server memory and wire size.

I want to point out a small correction here because I think it’s relevant.

LiveView, the DOM, and UI in general virtually always take the shape of a tree. I’m not sure exactly why this is the case, but it usually works out that way. The DOM is (of course) a tree, UIs are trees of components, and so on. Even in 3D rendering there is always a render tree.

So your data, the DOM, and the UI are all tree-shaped. The problem you’re dealing with is that pagination is not tree-shaped, at least not in your case. I think you are aware of this since your next paragraph describes the problem quite well.

This problem space is actually very old - things like virtualizing a folder tree view in a file explorer, but also providing drag/drop functionality. Unfortunately I think a lot of the knowledge for how to do this might have been straight up lost as we transitioned to the web and gave up on building good interfaces. The only place I have ever seen this mentioned is in this article (the “Yeet” section). The solution presented there is probably not relevant to your use-case, though, as this is an article about client-side state management and your state is in the database.

I’m sure there are more resources on this topic out there somewhere. I’m going to have to spend some time really digging at some point.

1 Like

Yes, I’m deeply aware that the HTML and the DOM’s internal structure is inherently hierarchical. Sometimes those hierarchies are essential for the correct structuring and layout of content but when your content is in itself also hierarchical or worse, recursive, such intertwined hierarchies can become overwhelmingly complex and frankly counter-productive.

I mostly agree with you there but not entirely. One person’s “giving up on building good interfaces” is another’s “drive towards simpler interfaces”, but I do agree that the result of the anemic set of primitives in HTML (compared to e.g. Win32 or OSF/Motif) made it really hard to build truly rich interfaces on the web. Remember Microsoft’s ActiveX controls and Silverlight and even Adobe’s now infamous attempt to replace HTML and Browser technology with Flash/Plex and Acrobat? Misguided and self-serving as all those were, they were in my opinion symptomatic of exactly that issue.

I’m a massive fan of a special kind of simplicity which stand in stark contrast with the crap you get when you try to simplify things by ignoring the parts of the problem that makes it complex, and useful. Simplicity that’s the result of absorbing and encapsulating all of the natural complexities present in any real environment behind a singular, simple abstraction that discards none of the complexities but instead fully deals with it internally while presenting a clear and simple concept or view of it outwardly, that’s the kind of simplicity I like. It’s not often achieved in real life, and in most of the cases where it is achieved it slowly evolves through sheer need. The road HTML had been on could be seen as a journey towards that type of simpliocity driven by the needs of programmers to get more done with less pain, but it is a long way still from where it really encapsulates all the complexities involved in designing and building a good user experience. What is genuienely rare, is for such types of simple abstractions of complex domains to be purposfully designed and built with that as objective. It isn’t often attempted and succeeds even less often. It’s what I am trying to do here, but it’s far too early to tell if my attempt will hit the mark at all. I know it will work for me, but I don’t know yet if it will ever benefit anyone else. Only time will tell.

By the way. You mention folder tree views in file explorer (email clients and word processors also used them for outlining) in the positive light of how good the tools used to be. Funny thing is, when I started this project that was what I had in mind when I thought of a tree on a screen. It was only when I really got into trying to visualise what this algorithm I needed was meant to do that I realised what a poor representation of a tree those were. It’s horses for courses. All those trees were used for secondary content like an outline or folder view shown nxt to the main content window. Vertical space was not an issue because it could scroll, but horizontal space was at a preium because using too much impeded on the space available to the main content. The result was that the branch / folder name was shown above its content with the content indented slightly. The “better looking” trees had connecting lines (which had fallen away in more recent times). The trouble was that if you had more than a few files or sub-folders in a folder the name of the parent folder would scroll out of view before you reach the end of the list of subfolders. Context is everything, and instead of showing the context of what folder(s) are being shown, all that left on the screen is a load of lines and these days, not even that.

I was so used to that for of tree that I never even realised that it was at best a dead tree, i.e. it was shown lying on its side. When I switched to a tree that showed the root at the bottom and the leafs at the top, I could suddenly fit a whole lot more data onto the screen and visualise a lot bettter what I needed to do. But more than that it meant I could use the space that used to be just lines and write the parent node’s detail centered underneath the child nodes and keep it there regardless of how I scrolled (left and right) through the children. That’s when I realised I had no alternative but to adopt that same layout for at least one of my application’s trees where the tree content IS the main content window and not some extra information shown alongside it. One could call it a revolution in tree display technology, or at least 1/4 of a revolution - 90 degrees. :slight_smile: It works especially well for me because in addition to the labels (like folder names) the content I need to display for each node is in itself a multi-line block of yet another instance of a tree display (my second use case for the library). The third use case is stranger still. That one’s derives from the fact that the data I display isn’t truly a tree but in some respects more like an inverted tree or network in that a node can have multiple “parents” or children in a different hierarchy. So if the user has navigated to some place in the tree or the “field of trees” they’ve walked into, they can choose an option to turn around and “look back” from there to see that other “tree”. I know that doesn’t make a great deal of sense at this point to anyone but myself, but it will become apparent in using the application.

1 Like

Maybe, but I doubt it. All the changes at assign level are one I’d have made, so I know those already. The changes I’m looking for are what LV determines internally based on what assigns were used to produce what part of the HTML that new needs to be rendered again because of the change. I.e. as you described it, the changes at the attribute level compared to the previous value held in memory. My entire approach hinges on determining and retaining only the essential bits of information in memory to detect the changes. I can’t afford to keep all the data in memory and I can’t afford to send data over the wire that didn’t change but LiveView doesn’t know it didn’t because it has no momory of what it was.

I’ve been referred to the LiveDebugger on chat. time will tell.

The dynamics which depend (statically, via @whatever) on the assigns which have changed will be re-sent in full. The static parts are not re-sent. If you look at your template and at the assigns which have changed, it should not be too difficult to infer what will be sent over the wire.

If you want to inspect the actual diffs on the wire, you can look at the browser tools like I mentioned. The diffs are in a JSON format.

If you’re using streams it’s your responsibility to “tell” LiveView which stream items have changed. It will re-send (dynamics for) those stream items in full. I don’t think there is any way to get more granular than a full stream item, there.

Presumably you must have access to some sort of “change feed” directly from the source (the database) here. Otherwise what you’re suggesting is essentially a paradox: you cannot know what has changed unless you have the previous version to check.

Yes, I got that. See next response.

It would present a paradox if it pertained to the same data, but it’s not the same data. I started with an overall strategy and reported success with the concept of splitting the tree content into two parts. The bulk of the data sits in the node content which naturally inludes in principle also the associations which renders it a recursive hierarchy. Using some cleverness on the server, I extract those association data (abstracted to a association by which each node has 0 or more child nodes in a specified order) into an entirely different dataset akin to another schema. This structural “schema” represents what the UI needs to know about how nodes (known solely by their IDs) are related to each other.

In the LiveView domain I handle the nodes and the structure data separately. After all the filtering and cutting out rectangular view sections, the structure data is small enough to be retained in memory as assigns and since it’s a single record it doesn’t even make sense to conider streaming that as it would always change anyway but without a previous one to compare to. The nodes, i.e. the big data it refers to and which gets rendered to HTML under its ID is a different matter. I don’t need LV to track the changes there because I already track those changes in my application, meaning I know about changes before getting to the presentation stage. When a changed or changing node is displayed or subscribred to, it will be updated as a whole, but here the whole “excludes” its children. Changes to the children will also go through, but under their own steam.

In the end, it comes down to this: As the user navigates through and interacts with the content via this tree view there is smart pagination logic behind it which has all the information it needs to build the correct view window structure from the database and can identify with “surgical accuracy” which of the nodes has to be updated, added because they were excluded from the previous ‘page’ or deleted because they are no longer needed. That’s the part that aligns with the Streams idiom whereas the minimalistic structure reflecting the structural essense of what is on screen is not designed for streaming but rather for direct assigns which renders and drives the container.

The job’s not done yet, so I would only be able to validate my understanding and assumptions about how LV’s JS side conspires with the DOM and how the container I use actually handle the style changes driving the tree layout holds true or breaks down once I have that running with extensive tracing at every level. It’s tense times, but I’m cautiously optimistic that the inevitable surprises will be technical issues since those always have solutions. It’s the so-called “soft-issues” like unforeseen impedence mismatches and divergent agendas that tends to trip us up.

1 Like

Would anyone want to start a repository with examples of every possible implementation good or bad to see the trade offs and what works and what doesn’t?

This has been an insightful topic with all your replies. I’m also working on a recursive tree in LiveView and wondered if I should use LiveComponents or regular functions.

I was looking if assign can track changes to each nodes in a map or list, but it looks like changing one node triggers a render for anything using the whole list. I thought of giving every node an assign based on it’s key or using a LiveComponent to assign for every node.

You can simply start with a function component and port it to a LiveComponent if you have performance problems. The programming model is essentially the same, the LC will just diff the assigns at the node level instead. Porting to/from streams is more complicated because the programming model is very different (imperative).

That’s correct. It would be nice if LiveView supported a key attribute like React so that we could avoid the LiveComponent trick in more cases, but for now they’re the best way to optimize this IMO.

You can’t do this because the assign accesses have to be statically known - that’s how the change tracking works (the @field syntax). Of course it would also blow up the atom table.

Dunno about an entire collection of good and bad options, but I have been working towards a repository aimed at arriving at a reusable separation of concerns which does what I need and hopefully what others might figure out they need to once they come to know more about it.

I was holding off making any of it public either until it was worthy of sharing with others (which might never happen) or until someone asks for it anyway (which might be what’s happening now). Make no mistake, nothng about it is complete or set in stone and it’s not something (yet) you can just clone, build and have fun with. I’m more than willing to put it out there if it could help someone who would also want to help build it into something that suits their needs too.

There’s very little about indefinitely recursive data that could rightfully be described as trivial, and as @garrison and I more or less discovered there aren’t all that many people with a significant appreciation of the problem space, let alone possible solution spaces. Even my own journey with it had been one of many mistakes, perspective adjustments and even tools which used to proclaim non-support for some database features which has come to support those features after I worked my way around them.

Before I get too carried away. Are you interested in me sharing ((GitHub’s what I use) the Work in Progress of my “future library” isolating the presentation of indefinitely recursive data in liveview as a separate concern? If you’d rather wait or go another router, no worries, but if you are keen and on condition that I can somehow explain to you where I’ve been heading with the work I’ve done so far would like to join the effort to turn it into something useful, then let’s go and get it done.

1 Like

Yes, I am interested in what you have so far and your explanations. Also, I’m interested what everyone has tried whether it failed or succeeded to see the trade-offs or reasons they failed.

2 Likes

I should mention this for clarity: in past discussions on this topic (on this thread and the others), I had the impression at the time that LiveView Streams were a bad fit for recursive/tree-shaped data because of the imperative API. There was also speculation (from both of us) that this was due to the complexity of the recursive UI, and so on.

In the intervening months I have ended up having to rip Streams out of every single use case I tried to apply them to, including the canonical use-cases presented in the docs (infinite scrolling feeds). It might seem like I’ve been a bit of a hater, but I really did try. At this point I am strongly convinced that streams are not useful for anything, at all, which is of course an extreme opinion which I do not expect anyone to take at face value. I will eventually write something long-form detailing this argument, with clear examples.

But yeah, to be clear, I do have skin in the game here. I did not want to rewrite my code, obviously, but the imperative API causes too many bugs in my case and there is no way around it. If your data does not need to be updated after it’s rendered this may not apply to you, but in any complex UI you are eventually going to be bitten IMO.

1 Like

In the light of which I propose that we expect participants to accept your perspective as the sum total of your experiences and the limitations of the tools you have access to, which might be different to mine. Let’s suspend any notion of defence or attack and focus on discovery and learning how we can overcome that what limits us. You may play devil’s advocate when you feel the urge and I will be the eternal optimist when i feel that way, but neither of us are trying to have our positions vindicated. I for one want to meet the expetations of my users which is for the vastness and complexity of the underlying data to simply vanish from their experience. I don’t which technology wins the day and which fails, as long as my users (in terms of simplicity and performance) and I (in terms of server load per user) get the best deal possible.

1 Like