squint 2025-09-29

Is this expected behaviour or should I file a bug? These work in cljs:

$ npx squint -e '(pr-str (assoc-in nil [:a :b] 1))'
$ npx squint -e '(pr-str (update-in nil [:a :b] (fnil inc 0)))'
$ npx squint -e '(pr-str (dissoc nil :a))'
The error:
Error: Illegal argument: assoc-in expects the first argument to be a Map, Array, or Object.

> get returns undefined in squint. update-in etc use get I keep running into this. I found that some returned nil when no matching item was found in a collection, and I had (th/assert-equal ...some result... nil). I changed it to nil? which works but it still feels like a bit of a footgun all these things returning js/undefined when cljs returns nil.

🤖 1

in CLJS undefined and nil are considered equal. So returning undefined should not be a problem. The reason we return undefined is that we can say "this thing wasn't found" without having to use contains?

👍 1

we could make some return undefined as well

or maybe I should just drop the get -> undefined thing but I don't think you should be running into undefined or null differences, that may just be an issue of your JS testing layer

Yeah it's the testing layer for sure, since Squint is internally consistent. I guess it just depends what experience you want people coming from real cljs to have. Of course Squint is not cljs, but the more like it it behaves the less teething problems people are going to have. 🤷‍♂️

People using cljs do interop with js of course and so if there are differences in interop that will explode sometimes.

cljs.user=> (undefined? (aget #js {:a 1} :b))
true

in squint JS semantics are preserved as much as possible. In JS undefined means "not found"

but I see your point. why do you test for explicit null?

btw clj-kondo has a new rule coming up where (= nil ...) is adviced to be rewritten to (nil? ...)

👀 1

it wasn't me your honour it was my magic computer brain

maybe you can tell it: when testing for explicit null always use ... - in your style guide

👍 1

the undefined vs null is a bit like your ref callback improvement

great idea i'll add that to my squint.md

bug/oversight

probably defaulting to an object should be the squint behavior

Will submit a bug, thanks!

I'm not sure if this is the same bug:

$ npx squint -e '(prn (update nil :a inc))'
{"a":null}
It doesn't throw an error but it should be {"a": 1} like cljs I think.

I see this:

$ ./node_cli.js --repl -e '(update nil :a inc)'
#js {:a ##NaN}

which makes sense maybe since incrementing undefined isn't defined?

ClojureScript 1.11.132
cljs.user=> (inc js/undefined)
##NaN

cljs.user=> (inc nil)
1

I guess get returns nil in CLJS and undefined in squint. I could change that, but I kinda like that behavior

so you know that the key wasn't there

you could use (fnil inc 0)

👍 1

Added the example

I don't understand why the nil turns into undefined, but I can use fnil as you suggested.

get returns undefined in squint. update-in etc use get

so nil doesn't turn into undefined, there was never a nil

Oh ok

Interesting, I didn't realize get returns undefined.

in squint it does, as I explained above

Thanks for that fix! 🙏

Oh never mind I see you fixed it.

I do'nt know what that does 🙈 will look it up.

I'll add it to the TODO list.

r/with-let is on my list already.

The r/cursor and r/reaction are untested and probably buggy.

create-ref basically creates an atom-kind of thing that receives the dom node, instead of the callback

it can be both in reagent

not urgent, just something I randomly tested

Oh ok interesting.

I can't actually find any examples of this

perhaps ChatGPT was hallucinating

in React you do have something like createRef, but you're already covering reagent's :ref thing!

The only instance on GitHub: https://github.com/search?q=%22create-ref%22+language%3AClojure&type=repositories&l=Clojure Because the repo is called _create_-_reframe_-app 😹

but since it's not supported in reagent, it's ok

Haha whoops. Looks like it's supported by rum and uix.

I guess we could easily support this if we wanted to. create-ref would just return an atom and instead of calling the callback you'd reset the atom.

Ah ok interesting. At the moment I'm just aiming for the small bits of the Reagent API in widespread usage.

Not opposed to it though.

yeah, I'd hold off for now

despite this being slop coded, do you know how it actually works?

😅 1

it seems to re-use DOM elements when it can

No it is honestly a bit of a black box to me. I mean I did read the code, but particularly fully-render-hiccup I haven't fully grokked.

Actually it's not that complex. There's a lot of special casing which was due to me giving broken things to the LLM.

There is a lot of strange stuff in there I would have done differently myself, but then I probably wouldn't have done it if I had to do it completely myself.

I asked chatgpt to generate an svg clock in reagent. works with eucalypt, no changes. https://squint-cljs.github.io/squint/?src=KG5zIGRlbW8KICAoOnJlcXVpcmUKICAgWyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2V1Y2FseXB0QDAuMC4zLytlc20iIDphcyByXSkpCgooZGVmbiBjbG9jay1oYW5kIFthbmdsZSBsZW5ndGggc3Ryb2tlLXdpZHRoIGNvbG9yXQogIFs6bGluZSB7OngxIDUwIDp5MSA1MAogICAgICAgICAgOngyICgrIDUwICgqIGxlbmd0aCAoTWF0aC9zaW4gKC8gKCogTWF0aC9QSSBhbmdsZSkgMTgwKSkpKQogICAgICAgICAgOnkyICgtIDUwICgqIGxlbmd0aCAoTWF0aC9jb3MgKC8gKCogTWF0aC9QSSBhbmdsZSkgMTgwKSkpKQogICAgICAgICAgOnN0cm9rZSBjb2xvcgogICAgICAgICAgOnN0cm9rZS13aWR0aCBzdHJva2Utd2lkdGgKICAgICAgICAgIDpzdHJva2UtbGluZWNhcCAicm91bmQifV0pCgooZGVmbiBjbG9jayBbXQogIChsZXQgW3RpbWUgKHIvYXRvbSAoanMvRGF0ZS4pKV0KICAgIChqcy9zZXRJbnRlcnZhbCAjKHJlc2V0ISB0aW1lIChqcy9EYXRlLikpIDEwMDApCiAgICAoZm4gW10KICAgICAgKGxldCBbbm93IEB0aW1lCiAgICAgICAgICAgIGggKCogMzAgKCsgKC5nZXRIb3VycyBub3cpICgvICguZ2V0TWludXRlcyBub3cpIDYwKSkpIDsgaG91cnMKICAgICAgICAgICAgbSAoKiA2ICguZ2V0TWludXRlcyBub3cpKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgOyBtaW51dGVzCiAgICAgICAgICAgIHMgKCogNiAoLmdldFNlY29uZHMgbm93KSldICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDsgc2Vjb25kcwogICAgICAgIFs6c3ZnIHs6d2lkdGggMjAwIDpoZWlnaHQgMjAwIDp2aWV3Qm94ICIwIDAgMTAwIDEwMCJ9CiAgICAgICAgIDs7IGNsb2NrIGZhY2UKICAgICAgICAgWzpjaXJjbGUgezpjeCA1MCA6Y3kgNTAgOnIgNDggOmZpbGwgIiNmZmYiIDpzdHJva2UgIiMwMDAifV0KICAgICAgICAgOzsgaG91ciBoYW5kCiAgICAgICAgIFtjbG9jay1oYW5kIGggMjUgMyAiYmxhY2siXQogICAgICAgICA7OyBtaW51dGUgaGFuZAogICAgICAgICBbY2xvY2staGFuZCBtIDM1IDIgImJsdWUiXQogICAgICAgICA7OyBzZWNvbmQgaGFuZAogICAgICAgICBbY2xvY2staGFuZCBzIDQwIDEgInJlZCJdCiAgICAgICAgIDs7IGNlbnRlciBkb3QKICAgICAgICAgWzpjaXJjbGUgezpjeCA1MCA6Y3kgNTAgOnIgMS41IDpmaWxsICIjMDAwIn1dXSkpKSkKCihyL3JlbmRlcgogIFtjbG9ja10KICAob3IKICAgIChqcy9kb2N1bWVudC5nZXRFbGVtZW50QnlJZCAiYXBwIikKICAgIChkb3RvIChqcy9kb2N1bWVudC5jcmVhdGVFbGVtZW50ICJkaXYiKQogICAgICAoYXNldCAiaWQiICJhcHAiKQogICAgICAoanMvZG9jdW1lbnQuYm9keS5wcmVwZW5kKSkpKQ%3D%3D

😲 3

Things like this I find questionable.

(defonce ^:dynamic *xml-ns* "")
And this:
(def ^:private event-name-map
  {:on-double-click "ondblclick"})
Why is this marked ^private lol?

where do you see this?

It's as if it's written by somebody with no brain...

in reagent onClick is also accepted btw, so maybe support this too

👍 1
👀 1

But a lot of time to mutate the code until the tests pass.

Will add to TODO.

I guess at some point it's best to stop using an LLM and cleaning up the slop and actually understand it? not sure

it works well so far though

Yes for sure it needs an actual brain to look through it and clean up.

This is why I'm a bit reluctant to release it.

I've never been afraid to release slop code written by myself, but that's just me ;)

It's almost not so much "look at this thing I made" but "look at this thing I found".

Ha ha ok that will be my inspiration. If it's good enough for borkdude it's good enough. borkdude

Discovered vs. invented again (!) --- that clock example is really something. 👏 🙂

I am horrifimazed by this entire journey

😆 1

great description

Why I think this is ok though. The code of eucalypt isn't that big and it works and has tests. I'm sure I could figure out in details why it works and find out how to improve it.

🙂 1

Famous last words. Narrator's voice: he would never find out, but used it for years ever after.

its like using a buggy version of a replicator on star trek. you order something and all the components are definitely there and it sort of even tastes right but its up to you to determine if it ACTUALLY has the right ingredients etc.

and many times to do so requires the same effort as doing it by hand in the first place

but I definitely get the fun side

if this lib is useful enough, I think it may be worth it to rewrite it by hand and heavily curate the code. it's like 500 lines or so?

💯 1

yeah if nothing else it skipped to proof of concept effort

exactly I love the idea of a tiny reagent/react replacement that I could use to write web components in lieu of lit for instance

replicant is even more desirable but I understand the cost of adding immutablejs sort of defeats the purpose

Perhaps replicant could also be implemented using deepEquals from es-toolkit, like eucalypt is doing. CLJS ends up doing deep equals often anyway

interesting about deep equals. I meant using eucalyptas in place of lit so there is some notion of reactive rendering etc which I appreciate from lit. That said I think there work with signals is very interesting and I hope it rolls out soon as part of all browsers.

and honestly your example there with just calling .render yourself is probably 90% of the time the best practice for web components.

☝️ 1

in general THANK YOU for squint. I feel like this sort of thing not being possible via regular google closure cljs has been an albatross around my neck for years. eg not being able to write cljs web comps that you can share with non cljs developers without them asking “why is this file size larger than the rest of my entire project?”

1
😆 1

That's really great to hear, thank you

I would very much welcome PRs to clean up Eucalypt, if I don't get to it first!

@chris358 is r/create-ref also supposed to work?

(not quite sure how this works normally in reagent)

https://squint-cljs.github.io/squint/?src=KG5zIGRlbW8KICAoOnJlcXVpcmUKICAgWyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2V1Y2FseXB0QDAuMC4zLytlc20iIDphcyByXSkpCgooZGVmbiBuZXctZ2FtZSBbXQogIHs6Ym9hcmQgKHZlYyAocmVwZWF0IDkgbmlsKSkKICAgOngtdHVybj8gdHJ1ZX0pCgooZGVmb25jZSBzdGF0ZSAoci9hdG9tIChuZXctZ2FtZSkpKQoKKGRlZm4gd2lubmVyIFtib2FyZF0KICAobGV0IFtsaW5lcyBbWzAgMSAyXSBbMyA0IDVdIFs2IDcgOF0KICAgICAgICAgICAgICAgWzAgMyA2XSBbMSA0IDddIFsyIDUgOF0KICAgICAgICAgICAgICAgWzAgNCA4XSBbMiA0IDZdXV0KICAgIChzb21lIChmbiBbW2EgYiBjXV0KICAgICAgICAgICAgKHdoZW4gKGFuZCAoZ2V0IGJvYXJkIGEpCiAgICAgICAgICAgICAgICAgICAgICAgKD0gKGdldCBib2FyZCBhKSAoZ2V0IGJvYXJkIGIpIChnZXQgYm9hcmQgYykpKQogICAgICAgICAgICAgIChnZXQgYm9hcmQgYSkpKQogICAgICAgICAgbGluZXMpKSkKCihkZWZuIHNxdWFyZSBbaV0KICAobGV0IFt7OmtleXMgW2JvYXJkIHgtdHVybj9dfSBAc3RhdGUKICAgICAgICBtYXJrIChnZXQgYm9hcmQgaSldCiAgICBbOmRpdiB7OnN0eWxlIHs6d2lkdGggIjYwcHgiIDpoZWlnaHQgIjYwcHgiCiAgICAgICAgICAgICAgICAgICA6Ym9yZGVyICIxcHggc29saWQgIzAwMCIKICAgICAgICAgICAgICAgICAgIDpkaXNwbGF5ICJmbGV4IiA6YWxpZ24taXRlbXMgImNlbnRlciIKICAgICAgICAgICAgICAgICAgIDpqdXN0aWZ5LWNvbnRlbnQgImNlbnRlciIKICAgICAgICAgICAgICAgICAgIDpmb250LXNpemUgIjI0cHgiCiAgICAgICAgICAgICAgICAgICA6Y3Vyc29yICJwb2ludGVyIn0KICAgICAgICAgICA6b24tY2xpY2sgIyh3aGVuIChhbmQgKG5pbD8gbWFyaykgKG5vdCAod2lubmVyIGJvYXJkKSkpCiAgICAgICAgICAgICAgICAgICAgICAgIChzd2FwISBzdGF0ZSB1cGRhdGUgOmJvYXJkIGFzc29jIGkgKGlmIHgtdHVybj8gIlgiICJPIikpCiAgICAgICAgICAgICAgICAgICAgICAgIChzd2FwISBzdGF0ZSB1cGRhdGUgOngtdHVybj8gbm90KSl9CiAgICAgbWFya10pKQoKKGRlZm4gYm9hcmQtdmlldyBbXQogIChsZXQgW3s6a2V5cyBbYm9hcmRdfSBAc3RhdGVdCiAgICBbOmRpdiB7OnN0eWxlIHs6ZGlzcGxheSAiZ3JpZCIKICAgICAgICAgICAgICAgICAgIDpncmlkLXRlbXBsYXRlLWNvbHVtbnMgInJlcGVhdCgzLCA2MHB4KSIKICAgICAgICAgICAgICAgICAgIDpncmlkLXRlbXBsYXRlLXJvd3MgInJlcGVhdCgzLCA2MHB4KSIKICAgICAgICAgICAgICAgICAgIDpnYXAgIjJweCJ9fQogICAgIChmb3IgW2kgKHJhbmdlIDkpXQogICAgICAgXns6a2V5IGl9IFtzcXVhcmUgaV0pXSkpCgooZGVmbiBnYW1lIFtdCiAgKGxldCBbezprZXlzIFtib2FyZCB4LXR1cm4%2FXX0gQHN0YXRlCiAgICAgICAgdyAod2lubmVyIGJvYXJkKV0KICAgIFs6ZGl2IHs6c3R5bGUgezpmb250LWZhbWlseSAic2Fucy1zZXJpZiJ9fQogICAgIFtib2FyZC12aWV3XQogICAgIChjb25kCiAgICAgICB3IFs6cCAoc3RyICJXaW5uZXI6ICIgdyldCiAgICAgICAoZXZlcnk%2FIHNvbWU%2FIGJvYXJkKSBbOnAgIkRyYXchIl0KICAgICAgIDplbHNlIFs6cCAoc3RyICJOZXh0IHR1cm46ICIgKGlmIHgtdHVybj8gIlgiICJPIikpXSkKICAgICBbOmJ1dHRvbiB7Om9uLWNsaWNrICMocmVzZXQhIHN0YXRlIChuZXctZ2FtZSkpfQogICAgICAiUmVzdGFydCJdXSkpCgooci9yZW5kZXIKICBbZ2FtZV0KICAob3IKICAgIChqcy9kb2N1bWVudC5nZXRFbGVtZW50QnlJZCAiYXBwIikKICAgIChkb3RvIChqcy9kb2N1bWVudC5jcmVhdGVFbGVtZW50ICJkaXYiKQogICAgICAoYXNldCAiaWQiICJhcHAiKQogICAgICAoanMvZG9jdW1lbnQuYm9keS5wcmVwZW5kKSkpKQ%3D%3D

🆒 1
🚀 2

Build size analysis Gzipped it's < 10kb

🔥 1