Any example of how to use the Dom API?

Although I like the simplicity of the result (I was never a fan of bs-webapi’s ergonomics myself), I feel like this refactor has some shortcomings if it is to demonstrate how to approach problems such as interaction with DOM APIs. Hoping that it is useful feedback for the team, I’m going to take this refactor at face value and give my two cents.

Take this commit for example: https://github.com/chenglou/domgraphs/commit/0727144626b7a6013d297b6aab74d056cb07f889#diff-6b86dad133536de8149349ea05fd8585R51

I think this only works when you’re refactoring working software to make the code simpler. It benefits from:

  • Having some code that was correct and performed the task well in the first place
  • Being able to look at and compare the generated JS output of some existing code
  • Having a good command of DOM API’s already before attempting to write this code from scratch

It is indeed easier to read and follow in places (due to bs-webapi’s API design and difficulty of making generic DOM bindings), but I’m not sure if it is easier to write, let alone easier to write correctly. Concerns such as false sense of safety or inaccuracies regarding nullability are important things to address of course. But knowing how often I get my hand-written bindings wrong, how hard they are to maintain when the underlying JS library keeps changing, and how many head-scratching bugs they caused me in the past, I don’t want to be in the mindset of “always interacting with JS” like when writing code such as context["moveTo"](~x=0.0, ~y=centerY). Thankfully DOM API is more stable than rest of the JS ecosystem and very well documented, but I would also be afraid to have this sort of arbitrary JS access all around a big codebase only to have the underlying library change its API making it a really error-prone upgrade process (instead of updating the bindings and letting the type system help with the rest of the work).

When building something such as domgraphs I heavily rely on tooling. With JavaScript and TypeScript you get a lot of help from editors such as VS Code. It allows you to discover APIs and get your code right (although it is sometimes unsound). libraries such as bs-webapi or Fable.Browser also provide a similar boost when interacting APIs as complex as DOM. Of course it is not a replacement for having a good working knowledge of the underlying API, but I would always want more assistance when writing code than having to have MDN or some JS libraries README always open on the side.

Another way of looking at it; Old browsers or different execution environments not having some features doesn’t invalidate the entirety of https://github.com/microsoft/TypeScript/blob/master/lib/lib.dom.d.ts, and I’m glad there’s a peer reviewed collection of types for DOM used by many people and written by people who have much more experience than I do.

For example, with let width = Belt.Float.fromInt(element["width"]) I definitely see myself omitting Belt.Float.fromInt if I don’t know the return type by heart. Or whether it can be null or not. In JS there’s already a culture of overly defensive code such as typeof element.width = "number" ? ... : .... People don’t start out writing code this way, but after one too many easily avoidable errors on Sentry, they adopt this pattern to interact with any and every function someone else wrote.

Re-creating all of this ecosystem of bindings with a small community and gargantuan API’s such as DOM that don’t always align with ReScript’s type system is an uphill battle indeed. Maybe ReScript tooling (not necessarily the compiler, but definitely the editor) can help out with this if it can seamlessly utilize TypeScript types to display hints when interacting with JS without bindings. Some editor features that would help with the following line
context["fillRect"](~x=0.0, ~y=0.0, ~w=width, ~h=height) :

  • I would like to have fillRect suggested as I type
    Personally, it is always a pain if I need to stop and google this sort of stuff. I might not know what this method is called, or how to spell it, or whether it exists at all. Autocomplete is a massive productivity boost.

  • I would like to see these labelled arguments suggested as I pass arguments.
    I really have no idea what order I need to pass them. Using labelled arguments help as documentation. But I first need to figure out what each of these positional arguments in one specific overload of the original JS function stand for and the accepted types.

    I also don’t know if I’m passing an unused argument to this function, or whether I need to (or can) pass more arguments.

    Slightly off topic; using labelled arguments in this case makes me think that I can pass them in a different order or I can insert another argument in the future at an arbitrary position, which is not the case.

  • I would like to be able to jump to definition and read signatures, docs. The destination could be lib.dom.d.ts#L3397

Of course this doesn’t help with poorly documented JS libraries much. There’s too many popular libraries with a single README.md acting as documentation containing things like “drawUnicorn: takes a canvas and draws a unicorn according to the options passed as the second argument” and having no other information. At least with bindings someone figures this stuff out once which helps everyone else. Unfortunately I also often don’t have the luxury of being able to say “I won’t use this poorly written library” or “All of the code we interact with in our project is bespoke apart from platform APIs”. I can’t count the number of times I was able to find someones hand-written Flow or TypeScript documentation for a JS library on Github without which I was unable to figure out how to use the library correctly in JS. I don’t think “with ReScript you don’t need bindings” tagline really clicks with me.

10 Likes

I’m quite new to ReScript, but more experienced with TS comparably. I can’t clearly understand what you’ve mentioned here. If I type something as string in TS, what makes TS can’t actually guarantee that it will never get undefined? How does ReScript achieve it over TS?

2 Likes

Hey @osener

but I’m not sure if it is easier to write, let alone easier to write correctly

I’d encourage you to try both ways and experience for yourself.

Regarding the other comments: The last step of the process would be to solidify those canvas helpers into a couple of externals, which means you won’t get them wrong. I’ve omitted that in the commits.

@moondaddi hello! Then you might have heard of various unsoundness of typescript. Tldr TypeScript’s type system is best-effort only for various reasons. If you google that you’ll find some links and examples.

ReScript achieves it because it was built from the ground up to be sound first and foremost (using OCaml’s type checker under the hood).

Well I’ve been using Reason+BuckleScript in production with projects and teams of different sizes for three years and have done this every which way imaginable. This was an honest try at giving some feedback, but I won’t comment on this any further.

1 Like

@chenglou I think I got your point and I read the doc again says about the differences between Typescript which is https://rescript-lang.org/docs/manual/latest/introduction I agree with your point and it could be the best choice for code readability and performance. But I have a small example here.

let (area, setArea) = React.useState(_ => 0.0);

let handleChangeArea = e => {
  let value = e->ReactEvent.Form.currentTarget##value;  // untyped value here
  setArea(_ => value);  // value should be converted to float from string
}

<input type_="number" onChange=handleChangeArea />

In this case, I can’t get any warning or error during write-time or compile-time. Only in runtime, I would find the error that I should convert the value to float inside setArea. I’ve done a couple of projects with ReasonML, I’ve been always facing the moment to make a decision somewhere around the boundary of interop to the outside of an untyped system such as DOM.

I think we all know that the value is always a string in DOM. But this error could happen in the runtime. So, writing codes based on the best assumption, and fixing in debugging time is not what I expect from ReScript. I would like to say that what I expect most from ReasonML and ReScript is reducing error in runtime.

1 Like

@osener I don’t believe I’ve tried to offend with what I said; we don’t know each other so I was asking you to consider it if you haven’t, which you subsequently said you did.

We accord more credibility when a user speaks from a position of having tried various different scenarios over long time. And I’ll accord that to you. Also, I think I did address all your other valid comments with my last paragraph.

@moondaddi I’m well aware of this particular case. You could try typing this with webapi and tell me what happens. The original ReasonReact used the approach you were thinking about.

In case you don’t feel like trying: currentTarget is exactly one of those that are almost infinitely polymorphic. The keys are not fixed, the types of values are not either. The best you can type it, if you went with a general approach, is a hash map of string to anything. Which still doesn’t get you any compile-time benefit. See my 4 cases in my reply to cvr. You’d end up with case 1.

In the same vein of idea:

I think we all know that the value is always a string in DOM.

It’s not; see the hash map comment. Hope you see what I mean now. That’s precisely why letting userland make their own external value accessor, which they can use on their specific, well-constrained target type and which returns string, lets you prevent this bug.

2 Likes

All the messiness of DOM APIs notwithstanding, I think all the osener’s points are valid, and to the extend quality bindings to DOM are at all possible, it would be great for us devs if this problem was solved in one place for everybody. I understand that it’s not likely to be solved in the near future, but without solving it at all, I think ReScript is going to be a hard sell to a lot of organizations.

4 Likes

it would be great for us devs if this problem was solved in one place for everybody

There are several topics here concerning what I first wrote.

  • One is type safety, where there’s the misconception of loss of type safety, but in reality there isn’t. Lack of final commit for turning the canvas method accesses into external helpers notwithstanding.

  • One is folks’ general tendency of trying to stuff a shiny feature into a product instead of working from the product back to the tech, which I’d actually like more discussions on (not here though), which is valid disregarding the features discussed in this thread and very under-discussed.

  • Another one is is my comment of removing most of my own refactor in my link due to the UI needing wholesale revamp, obsoleting most of my own effort and our technical discussions here. I kinda wished someone asked about that. This should be our priority number 1. If anyone starts a new thread on this discussing the real valuable things on this topic, I’d be very happy =). But before obsoleting most of discussion here, I wanted to make sure that the basics are clear.

  • Last one is per-app specialization of bindings vs a single global binding, that you’re talking about. I felt like I’ve explained enough that for the case of DOM, this isn’t a good idea. We’ve already established that a complete and sound DOM bindings will end up being giant hash maps of option<'anything> . That’s worse for developer ergonomics *. The same time it takes you to learn those binding apis is better spent if you just bind to your subset (because you definitely aren’t using the full DOM), encapsulate them in a few places (like in my last commit) and use those. Most of what osener said still applies. Well, there are some nice little object/records you can put here and there, but surface area isn’t the same as usage frequency. See moondaddi’s use-case, which is most of what devs use.

Btw the last one really isn’t much of a controversial opinion; I’m not sure whether it’s because folks feel like the grass is greener on the other side, or some other reasons. If that’s the case, here are some random google results on TypeScript having the same problem, needing the same type of solution: https://github.com/microsoft/TypeScript/issues/299 https://brightinventions.pl/blog/5-ways-to-benefit-from-typescript-in-react. Even with TS’ rather intense type system, this thing is relegated to userland patches. At least we’re advocating for clean, specialized bindings instead of casts!

This thread might be getting too bloated for the question. But I’d like to add hopefully one last:

  • Solving things in one place for everybody is less of a good idea relative to what you might think.

Of course, as lang/ecosystem developers, we do try to solve things in one place, to a reasonable extent. But far too often you’re much better at properly solving your one or two use-cases, than we are at solving hundreds of use-cases at once.

Also, for hoichi or anyone else: if you’d like, you can pass me some of your public ReScript DOM code, and I can attempt to tweak them using what I said above. I’ll be happy to do it.

* Some nuances:

  • For canvas in particular, it’s actually reasonable to have a single bindings location in stdlib. So we could do that.
  • General DOM api bindings in stdlib is still possible, for a select subset of DOM apis that are clearly bindable without being obtuse. But again, the bulk of your use cases are like querySelector, target, value and others, which a general, centralized binding doesn’t help with. As seen in the TS links above, that’ll end up with us binding to e.g. target as an abstract type.

This has been a nice thread btw. I’m glad we’re surfacing these perspectives and engendering better thoughts.

4 Likes

In our experience this applies really well to the general “Bindings dilemma”: When Reason / BuckleScript was pretty new (like 3-4 years ago), ppl started writing bindings for JS libraries and quickly realized that 1) it’s really hard to type the whole library, 2) that the bindings highly depended on the use-cases of the organization using it, and 3) it’s hard to keep the api similar to the JS counterpart.

So they had to do a decision: only type the parts they use in a highly specific way, or try to spend a huge amount of time trying to type the whole surface. Relying on a crowd-sourced solution is hard, because you need to find the right ppl that have the same set of use-cases, and also are in the same mindset of api design.

What I learned from talking to a lot of production users is that companies tend to build their bindings in private, because they don’t think they are generally applicable for a broader audience; in the best case they contemplated on cleaning up their bindings and then release them on github… I think until now there have only be a handful of bindings that actually made it that far. Oftentimes the upstreaming was followed by abandoning the bindings, which is also a huge burden for us to keep up with.

The second attempt we saw (actually multiple times already and ppl burned out hard on that one): Try to automatically convert the TypeScript d.ts files to ReScript. This general idea kinda worked for a subset of d.ts files, but quickly broke down on any other non trivial definition of popular JS libraries. One might assume that the type systems are kinda similar enough to do this kind of conversion, but TypeScript is an unsound type system, so you will struggle with every type hole you stumble upon.

Anyways, I agree, maybe there must be some middle ground where we can provide some support for common DOM scenarios. I think there can be this hybrid solution where ppl grab a specific set of properly curated, very basic bindings just to get going and adapt them to their needs.

It’s definitely not easy at all, but we also don’t want to gate-keep users out that just want to build a product, but get stuck every other keystroke because they think they absolutely need to properly write generalized bindings for every little detail they use in their app.

2 Likes

I think at the end of the day, it all depends on how skillful you expect the ReScript teams to be. I know Paul Graham said that in the long run, standard libraries (and by extension, bindings) don’t matter, but I’m not sure an average frontend dev coming from JS/TS is as talented a hacker as Paul Graham.

I’ve worked with quite some people that saw TypeScript in general as a hindrance. In fact, even, say, React experts like Kent. C. Dodds only add types after they’ve figured out the logic. And TypeScript is closer to JS semantics and comes with excellent IDE support: autocompletion, parameter hints, and other productivity boons. Personally, of course, I likeReScript way better; my point is that a lot of working developers don’t have a firm grasp on types in the first place, and expecting the teams to roll their own bindings seriously narrows the potential reach of ReScript. (Also, even being able to write those bindings, I think most devs would rather spend their time modeling data and programming business logic, not being lost in technicalities.)

Having said all that. Firstly, after all of the explanations, I now see the scale of problems with generic DOM API bindings better. And secondly, I very much respect the ReScript team’s decision not to offer the community the half-baked solution, riddled with false security, bloat, and so on. Thank you, people!

13 Likes

@hoichi yeah, I think that’s a good wrap up to this discussion unless folks have other questions about the main topics. One big clarification though, so that folks reading it don’t get the entirely opposite conclusion from your post:

We are not aiming for the language philosophy PG advocates. I hope that this is clear throughout this post and my refactors.

  • Contrary to PG’s opinion, standard libraries absolutely matter for day-day programming. We’re not a research language.
  • Bindings matter to us. Heck, in terms of pure quantity of bindings, you saw that I was advocating for more bindings instead of less (less, in this case, would have been to e.g. putting them in a single place in stdlib).
  • And so what we’re demonstrating here is not how experts should do it. We’re doing the opposite: throughout this thread we’ve explained how to simplify the codebase, how to remove the extra learning overhead of several language concepts and a big library, and doing so without losing type safety (if this last one sounds debatable, see my explanation in my previous post).

So we’re actively trying to simplify things, precisely for the masses of programmers. It’s the process of simplification that requires expertise, but once that’s demonstrated, any dev can imitate it. The result of the refactors above shows that; the end result is newcomers-friendly, as opposed to attempted usages of libraries and language features even experts have trouble navigating.

expecting the teams to roll their own bindings seriously narrows the potential reach of ReScript

For now, we can never truly remove the need to understand bindings, because even installing a pre-written binding requires basic understanding of what the library did if you want to use it in a nontrivial way (though we do try to diminish such need by making ReScript data structures map to JS more cleanly). Consider that if we assume bs-webapi is the way to go, a newcomer will definitely have to understand what’s going on in there to use it.

If bindings aren’t avoidable, it’s better to fully expose and document them instead of trying to hide them and let newcomers be confused longer.

6 Likes

I can’t disagree with your approach. Thank you!

FWIW, it matches well with React’s approach of being as explicit as possible and relying on the basic understanding of language features. I think it’ll be a big plus for everyone using ReasonReact.

2 Likes

I was busy when you pinged me, sorry for not responding earlier, and I mostly forgot about this until today. I’m now glad I did because I would have had a very angry response. I’m glad to see some of the other responses were along the lines of what I would have said.

I am disappointed. I thought you had bigger goals for ReScript than toy projects and small bespoke applications. In big multi-layered projects such as the one I work on your resulting code is completely unacceptable. I’ve read your commit messages but that only solidifies my impression of the style you seem to be targeting with this approach.

I am not at all interested in the simplest possible application code that produces identical JS to more complex compiler-enforced bindings. This to me sounds like throwing away significant benefits the type system can provide for some misguided sense of convenience. I need a library that developers who are familiar with developing in TypeScript using lib.dom.d.ts will be comfortable with; those are the people I think should be the most interested in ReScript. Certainly that’s the sort of developer who I will be guiding as my company expands our use of reasonml/rescript to other teams.

An open Js.t document object that lacks a list of available methods and won’t even trigger a compiler error from a typo is not something I will ever ask developers to interact with. Pretending things aren’t optional when the DOM specification says they could be is also not interesting to me; the codebases I work on are so large and so deeply tied to the DOM that such assumptions can (and have) bitten us through weird edge cases customers find when the null/undefined case actually comes up.

Finally, faster build times don’t really interest me as much as smaller bundle sizes (this is becoming a wider issue with ReScript). Our TypeScript code takes 60-90 seconds to clean compile so even a slow ReScript build is a huge win.

I might look to use your techniques in my library implementation where a direct external is not appropriate, but the application code that uses the library will never look like the style you seem to be aiming for.

The more time that goes by the more I’m convinced I will end up rewriting/forking bs-webapi to build a pipe-first version of it that is more approachable, leveraging records-as-objects and poly-variants-as-strings.

9 Likes

I don’t want to get knee deep into the argument, but I’d (also) consider it important to recognize that not all dependencies/bindings are created equal.

Some 3rd party JS libs get a version update every few months and might contain an API that is hard or even impossible to type universally. Even the code quality and internal type safety might be questionable at best. In those cases it’s probably good to be practical and implement simple local bindings, finish up early and go spend time with family or friends.

However, DOM API is part of web platform. It’s something that pretty much all of us must use. It also doesn’t change all the time, so maintainability from backwards compatibility point of view is not such a huge issue. It might be complicated to bind some specific cases and the API is not very “functional” by nature, but we as a community should still aim for the most accurate and safe representation because the work will benefit pretty much everyone.

Fwiw, after the initial learning struggles, I quite like the current Bs-Webapi. There are things to fix/improve but nothing impossible. I’d also gladly use a pipe-first version.

3 Likes

I’ll be honest , a couple principles that have been brought to light in this thread concern me.

The number one thing that bothers me the most is how it seems like the Rescript team is forcing the community to go down a coding style , which seems to be based solely on personal preference. I don’t understand why that is even a goal ? Let the community manage itself , I have never seen users of a language be corralled like that.

Secondly , the coding style proposed here seems like just Javascript with extra steps and no editor support to me. What’s the point ?

1 Like

Please be reassured that we will make some suggestions but it’s not in our interest to enforce a style (and we can not do it either, there’s only type checker in the compiler not linter).

In most cases, we would like to keep users informed about the trade-off between various styles, but in the end it’s up to users to make the judgement

5 Likes

The only thing we did was providing another perspective on the topic, which of course is subjective, since we are ReScript users ourselves (I wouldn’t even know how to give recommendations objectively?). We don’t enforce any styles, we are just worried ppl get all busy spending their whole productive time in writing untypable bindings, and we want our users to learn the interop layer properly before they start using blackboxes (and it’s very easy to get blocked by missing pieces of bs-webapi that’s for sure).

To be clear: Feel free to do whatever makes you productive. Users like @spyder just expressed their thought to fork bs-webapi to adapt it to pipe-first etc, and I would be very glad to see a more idiomatic rescript-webapi binding codebase for sure (makes it easier for me to copy / paste / adapt stuff for my own code).

3 Likes

I appreciate the sentiments, and apologies for continuing the now-very-off-topic discussion, but these comments:

Do not jibe with this one:

The impression I get from @chenglou’s tone (on many threads, not just this one) is prescriptive and not at all encouraging of alternate styles. Maybe it’s just the wording he chooses, but the pattern has been fairly clear for a while and it’s my biggest concern with how the ReScript transition has been handled.

4 Likes

You don’t get to belittle my tone, my work, and turn a thread that ended on a high note into one that ends with a bad note, when you yourself write things like:

I am disappointed. I thought you had bigger goals for ReScript than toy projects and small bespoke applications. In big multi-layered projects such as the one I work on your resulting code is completely unacceptable.

And you clearly came in with a preference set in stone and selectively ignored what I said. But for the sake of clarifying misconceptions one last time, because most folks check the first few and last few messages only, here’s what I said, re-re-re-summarized:

This to me sounds like throwing away significant benefits the type system can provide for some misguided sense of convenience

Please reread this thread. The types are more correct. That correctness happened to provide some simplicity, which resulted in convenience. The previous types accepted illegal states. Those ended up with the direct impact that downstream usage of those also ended up handling the illegal states in a wrong way. The usages, and even UX, suffered because of it. If you still don’t believe me, start from before my commit, and go through the refactoring yourself. Please try it.

An open Js.t document object that lacks a list of available methods and won’t even trigger a compiler error from a typo is not something I will ever ask developers to interact with

I’m not advocating for this. Did you read my commits messages? That was for iteration clarity purpose, one step before solidifying the bindings into proper, more type-safe bindings catered to the codebase’s need. How else am I supposed to show intermediate work? Are you gonna start telling me some of my local branches don’t type check?

the codebases I work on are so large and so deeply tied to the DOM that such assumptions can (and have) bitten us through weird edge cases customers find when the null/undefined case actually comes up

Same as above. Those assumptions are meant to be tightly encapsulated in a single module, likely with a few dedicated abstract types for specific dom node or usages. We’ve even discussed on stream about this before; your nulls came from bad unsafe casts and assumptions that got carried from one point to another transitively, instead of (again, like I said on stream) encapsulated and closely guarded in a single location. The unsafe casts gave you no safety. Adding an option type in your base library because of one callsite (instead of making a dedicated helper for said callsite which avoids dragging down all the other existing usages) does work, except it’s super unclear for all the existing callsites why they started handling extra states (that never exist). Of course, if you end up having 3+ exceptional callsites, then you backtrack and revisit that specialization, rinse and repeat.

The more time that goes by the more I’m convinced I will end up rewriting/forking bs-webapi to build a pipe-first version of it that is more approachable, leveraging records-as-objects and poly-variants-as-strings.

You can do that, but this situation isn’t about pipes, and you’ll find very few relevant records (in terms of real-world usage, not in terms of api coverage). For example, you can go type things like event target. When you end up with the equivalent of an any, you’ll end up getting no editor completion. Whereas what I tried to advocate above will give the userland of that specific codebase proper IDE-driven help.

Do I want a generalized library that can provide good type-driven IDE help? Yes. Have we/I been writing such libraries for the past four years? Yes. Did we succeed in doing this with DOM? No we didn’t (bs-webapi):
Screen Shot 2020-11-02 at 2.08.08 AM

The impression I get from @chenglou’s tone (on many threads, not just this one) is prescriptive and not at all encouraging of alternate styles

As a small community:

  • The language itself stays, as much as possible, unopinionated. Like bob once said, we don’t try to be a dumbed down “big agenda” language, but we also don’t try to be a bloated language. Walking this fine line means conflicts are inevitable (but the results, worthwhile). That’s why I personally can’t and don’t mind conflicts; however, I greatly care about how people solve conflicts. Rageposting and belittling other’s work isn’t one of them. Whenever I want to modify someone else’s work as a showcase, I reach out, in public and in private, to ensure I have the permission.
    There are lots of things you posted here and elsewhere that I strongly disagree with, but dare I say I never publicly attacked your work. Why can’t you do the same? Has this been worth it?
  • I do have opinions, but I’ve also shown that I back down when I disagree, just so that the team and the community can stay united. When disagreements can’t be solved, I’ve walked the walk, and have many times chosen the locally suboptimal route with a personal discomfort, for a hopefully globally optimal direction for the team and community (every team member has done so too). Heck, I didn’t even start the rebranding; a few key folks internally and externally advocated for it, and I understood that I was one of the only ones capable of gathering enough activation energy from different folks to implement it, so I friggin carried the load. I didn’t even complain when folks low-key subtweet me here and there saying random stuff like “Cheng Lou is breaking things up”. Just because I advocate for something for the team, newcomers, and/or community, so that we can have some recognizable face and direction, doesn’t mean I personally espouse the idea. ReScript isn’t even officially my job until a year ago; and tbh the more irreconcilable and puristic software letter tweaking I see, the less I want to make it my job.
  • Crucially, when there’s a disagreement, I myself don’t directly reach for the end-all argument of “why aren’t you accepting of my alternate style?”, because there’s no good defense my teammate can summon if I straight up say that.
    As a side remark, this tendency seem particularly strong in the FP community, where folks tend to overgeneralize not just the code but also the stance. Every paradigm yak shaving seems to need to show some kind of ideology or aspiration; this is suffocating and leaves less room for actual ideologies discussions. “High priests of a low cult”, like Alan Kay once said.
    A small community requires curation, emphasis and unity to succeed. Every single decision can have someone reach for the kind of argument along the line of “you’re not respecting my style”. Fortunately in most circumstances, people don’t say that, and choose instead to ask why, and in the usual worst case, agree to disagree. There can be no curation, emphasis and unity if everyone constantly turns a small technicality into a freedom-related issue.

Since the thread already got derailed, and the “vibe” here turned bad, I’m gonna make an exception and lock a thread for the first time. Apologies.

10 Likes