Option to pass a --no-daisy flag when creating a new phoenix project

The problem with composition is much deeper than you suggest. LiveView’s component structure is completely flat. Components are mounted with global ids, and the responsibility of choosing those ids is passed onto the developer. The id requirement itself does not compose; each parent must pass an id to its children, which means it must have an id of its own, and so on. The concerns of the components become quite badly intertwined, defeating their purpose. It’s actually a pretty textbook example of what Hickey was talking about.

I do not know LV’s engine in extreme detail, but I know enough to know that it’s mostly template-based and treats conditionals as first-class citizens. This structure is actually very problematic, because it means the component tree is not a strict superset of the DOM tree as it would be in, for example, React. That in turn makes implicitly mounting stateful components by their tree positions very messy if not outright intractable. So instead the engine cheats with global ids.

This can be fixed, but it’s not easy. The whole thing probably has to be rewritten from scratch, the template approach thrown away in favor of a full element-level tree, the server/client protocol rewritten, and so on. Backwards compatibility would likely be broken because it’s good to know whether a component should be unmounted during the local diff cycle, so the “teleportation” ability should probably be removed.

It’s a lot of work. So the question becomes: does anyone using Phoenix actually care? And given that you are the only individual I have ever seen (other than myself) voice this concern, I think the answer is “not really”.

This actually cannot be fixed as far as I can tell. It’s an inherent tradeoff with latency.

I strongly recommend you avoid reading the thread where I unsuccessfully attempted to explain this to I think at least four different people lol.

Strangely it is actually possible, as after a week of wasting my time thinking about it I realized that you can abuse the client-side phx-remove binding to run a callback on the server. Utterly cursed, of course.

It can. I did it last year for my job. The trick is that, while you can’t get strongly consistent rendering, you can get eventually consistent rendering. Client components just have to track both their state and what the server thinks their state is, and then you version every state change. If the server’s state is the same version as the client’s state, it takes precedent, if the client’s state is a version ahead of the server’s state, the client’s state takes precedent, but only until the server gets the update and sends back its copy of state for that version.

What gets really tricky is when state changes don’t originate from the client. To handle that I needed to implement the notion of branching. Effectively, when the server pushes an update all on its own, it is forking the version history and the client has logic to merge that forked version history back into its own. In our case that logic was basically to just let the server entirely overwrite the client’s state, but it could be a fancier CRDT based solution if needed. E.g. multiplayer form editing where you want multiple people to be able to type in the same field at once

Anyway, point is, it can be done, though you’re right it does require giving up strongly consistent rendering. My bigger point is that this is absolutely something I would expect a framework like LiveView to do for me, or at least to make straightforward to do myself.

wait, if I’m actively focused and typing in an input, the server should just be able to update that input right under me? I think if I ever saw that, I’d consider it a bug

2 Likes

No, that’s not what I described.

I wrote a framework for data loading and injection à la React Query (now TanStack Query I think) that had to solve this exact problem. The issue with phx-remove is that it’s not called when a parent of a component that has phx-remove is removed. It’s only called when the component is removed directly. The more reliable, though equally silly solution is to use the internal telemetry event that live view emits when it removes a component. That was added relatively recently though.

Before that I was doing some process state dark magic to make every component that read data record itself in a lookup table then after every render I checked which components that “should” have rendered didn’t. It work reliably, but trying to explain to people the value of such a library was too draining. It was/is called LiveQuery if you’re interested.

2 Likes

I guess this hinges on what “can be fixed” actually means. I understood what you’re suggesting but it makes me uncomfortable.

For example, take a simple controlled component that just upcases its input. With latency, your solution would produce:

f -> F -> Fo -> FO -> FOo -> FOO

With more latency:

foo -> FOO

Note that this simple case is in fact conflict-free and will always converge to the correct solution. But it displays intermediate states that are not valid under the user’s expectation, which is that the contents of the input should always be uppercase.

They might make a valid CRDT if you declare that those states are valid under the rules of your CRDT (you can make up any rules!), but in practice I think you will have a hard time designing a general API that solves this problem. The fact that “multiplayer editing” solutions are often extremely domain-specific is a good hint.

That’s ridiculous and should be considered a bug.

Huh, the more you know. But like you I simply refuse to live like this.

I mean, yeah, you can’t magically make latency go away. If the sever is responsible for updating, you gotta wait for a round trip to see the result. That said, with normal typing speed you’d probably get

“f” → “fo” → “foo” → “FOO”

because the sever replies would always be behind until the user stopped typing. In this case I’d probably just suggest the client do the upcasing though.

The place where this comes in handy is places like live updating select inputs where you can search for options and that hits the DB. That fundamentally doesn’t work with vanilla live view. You’ll notice all the libraries (aka LiveSelect) that try to do it end up exposing imperative APIs to update the options (e.g. you have to call send_update).

1 Like

What I’m getting at is there are classes of problem for which something like foo -> FOO is an unacceptable (i.e. broken) solution, and this cannot be fixed in general. You can make latency very small by rendering on the client and this is exactly what e.g. React does by default. This is not an accident btw, modern React has an engine that is entirely capable of running asynchronously and the fact that input events trigger a synchronous render is an intentional choice, because otherwise you would have exactly this problem.

LiveView could fix a lot of things, just not this one particular thing. Maybe there is some API that would make it less bad, though. Personally I think it would be better to always client-render components for which this is a concern (also an imperfect solution).

Note that this is an edge case where users have been extensively trained to accept the unnatural idea that search results are out of date for the query they’re typing.

Yeah, that’s fair. I would agree, logic that doesn’t need to be run on the server should be run on the client, but self proclaimed backend devs don’t like to write JS, and so users suffer…

1 Like

I think your discussion should be moved to an own topic.

It doesn’t have very much to do with “should the installer have a no daisy flag” IMO.

Should it have a flag? Always

But let’s nog forget that a larger project is made once in a while while hobby projects are made al the time (POC included). So having it as default saves a lot of time compared to to not having it.

Just to add a personal data point: I hate tailwind with a passion, and I also hate DaisyUI with a similar degree of passion.

I also didn’t particularly enjoy the flip-flop from webpack to brunch to webpack again (?) to esbuild to whatever “cursed” JS build step the tailwind library uses. In the middle I remember having bootstrap, some other classless framework at some point.

I think the default should be either something old and stable (such as bootstrap) or some classless CsS framework.

But ultimately it’s up to the framework devs to decide what to pick, and those choices are often based on their personal experience using Phoenix.

1 Like

I am very sympathetic to @AHBruns 's response to this (it’s more important to cater to the serious users), but I think an even more salient point to make here is that due to the advent of LLMs the hobby project segment basically no longer exists.

These hobby users do not need pre-built components anymore because they will just generate their projects with a model. If anything core_components should be thrown away and replaced with an LLMs.txt or whatever the cool kids are using. Anyone serious was deleting them first thing anyway.

4 Likes

The Tailwind CLI is written in JS and compiled into a native binary with Bun. At this point Tailwind may as well be Bootstrap so I don’t think it’s an unreasonable choice.

I dislike this. The core_components.ex show newcomers very nicely how to write components.

Any seasoned developer can just delete them which takes like 20 secs?

3 Likes

I think you are assuming that

  • The core components are good, which they are not (that’s why serious developers delete them)
  • Generated components are the best way to teach newcomers, which they are not (guides are better and we already have them)
  • Newcomers are using the core components, which they are not (they’re using Claude)

I understand the argument for having something that works out of the box, but I also think the tide is clearly turning another way. If you don’t see this now, you will in 6 months.

Strongly agree here. I think there are various parts of Phoenix which prove a desire to make it work well (look decent, decent patterns in place, security etc) for newcomers out of the box. I understand the adoption motivation I presume is behind this but as others have said I think it goes too far and with LLMs now a lot of it seems redundant. I really like the idea of an LLMs.txt that supports bootstrapping a decent project out of the box as a replacement/potential new direction.

Hell I might even use that myself whereas I don’t touch any of that stuff right now. Previously I just used my own existing code (or occasionally someone else’s that seemed better) as a template and increasingly one of my main use for LLMs is to bootstrap something new to me so I can use it as a starting point and/or reference as I also work through docs/guides.

1 Like

Can a serious developer have a hobby-project? Is every serious developer only making large projects?

I do know cool kids that cannot spend the money for LLM’s and Agents. Companies can. So let the bots remove DaisyUI. If they can’t, they must not be serious bots cause you know, serious bots always do.

Ergo: your argument can be reversed. If LLM’s can do anything, there is no issue in having Daisy and CoreComponents.

There are various argument errors in your post many of which come down to “if you don’t agree you are dumb”. I suggest you write a little more considerate :slight_smile:

7 Likes

Last time I checked one is guided from “Installation” into oblivion. Yes, everything is in the guides but calling them ‘easy to follow’ is another thing.

1 Like