squint

Chris McCormick 2025-09-29T11:00:06.647339Z

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.

Chris McCormick 2025-10-23T07:11:21.735809Z

> 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
borkdude 2025-10-23T07:13:51.087939Z

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
borkdude 2025-10-23T07:16:52.108799Z

we could make some return undefined as well

borkdude 2025-10-23T07:17:37.098929Z

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

Chris McCormick 2025-10-23T07:20:02.901629Z

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. 🤷‍♂️

Chris McCormick 2025-10-23T07:20:27.405229Z

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

borkdude 2025-10-23T07:24:46.049069Z

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

borkdude 2025-10-23T07:25:17.459419Z

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

borkdude 2025-10-23T07:25:47.959229Z

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

borkdude 2025-10-23T07:26:55.938259Z

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

👀 1
Chris McCormick 2025-10-23T07:27:00.367729Z

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

borkdude 2025-10-23T07:27:09.669199Z

lol

borkdude 2025-10-23T07:27:35.560829Z

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

👍 1
borkdude 2025-10-23T07:28:56.862109Z

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

Chris McCormick 2025-10-23T07:29:24.228979Z

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

borkdude 2025-09-29T11:05:07.760609Z

bug/oversight

borkdude 2025-09-29T11:05:23.903809Z

probably defaulting to an object should be the squint behavior

Chris McCormick 2025-09-29T11:05:44.368739Z

Will submit a bug, thanks!

borkdude 2025-09-29T11:10:36.637719Z

thanks!

Chris McCormick 2025-09-29T11:22:53.529199Z

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.

borkdude 2025-09-29T11:28:06.898929Z

I see this:

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

borkdude 2025-09-29T11:28:23.163429Z

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

borkdude 2025-09-29T11:32:16.471329Z

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

borkdude 2025-09-29T11:32:29.274139Z

cljs.user=> (inc nil)
1

borkdude 2025-09-29T11:32:56.408589Z

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

borkdude 2025-09-29T11:33:06.095499Z

so you know that the key wasn't there

borkdude 2025-09-29T11:34:35.642389Z

you could use (fnil inc 0)

👍 1
borkdude 2025-09-29T11:50:15.419459Z

Added the example

Chris McCormick 2025-09-29T11:51:42.207759Z

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

borkdude 2025-09-29T11:52:10.711839Z

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

borkdude 2025-09-29T11:52:33.034839Z

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

Chris McCormick 2025-09-29T11:52:48.523809Z

Oh ok

Chris McCormick 2025-09-29T11:53:03.112609Z

Interesting, I didn't realize get returns undefined.

borkdude 2025-09-29T11:53:18.391559Z

in squint it does, as I explained above

Chris McCormick 2025-09-29T11:54:39.925219Z

Thanks for that fix! 🙏

Chris McCormick 2025-09-29T12:04:20.176549Z

Oh never mind I see you fixed it.

Chris McCormick 2025-09-30T10:58:48.931929Z

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

Chris McCormick 2025-09-30T10:59:18.637009Z

I'll add it to the TODO list.

Chris McCormick 2025-09-30T10:59:45.840719Z

r/with-let is on my list already.

Chris McCormick 2025-09-30T11:02:45.786939Z

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

borkdude 2025-09-30T11:05:59.652499Z

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

borkdude 2025-09-30T11:06:08.382959Z

it can be both in reagent

borkdude 2025-09-30T11:06:30.681269Z

not urgent, just something I randomly tested

Chris McCormick 2025-09-30T12:42:26.026119Z

Oh ok interesting.

borkdude 2025-09-30T12:46:56.957669Z

I can't actually find any examples of this

borkdude 2025-09-30T12:47:02.830099Z

perhaps ChatGPT was hallucinating

Chris McCormick 2025-09-30T12:47:27.851709Z

Lol

borkdude 2025-09-30T12:48:39.428159Z

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

Chris McCormick 2025-09-30T12:48:46.559479Z

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 😹

borkdude 2025-09-30T12:49:01.456599Z

lollll

borkdude 2025-09-30T12:56:28.294219Z

you forgot to look in the code instead of repos: https://github.com/search?q=%22create-ref%22+language%3AClojure&type=code

borkdude 2025-09-30T12:57:06.485669Z

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

Chris McCormick 2025-09-30T13:00:34.969559Z

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

borkdude 2025-09-30T13:01:57.689719Z

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.

Chris McCormick 2025-09-30T13:02:49.772039Z

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

Chris McCormick 2025-09-30T13:03:12.590869Z

Not opposed to it though.

borkdude 2025-09-30T13:04:25.962729Z

yeah, I'd hold off for now

borkdude 2025-09-30T13:04:58.552419Z

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

😅 1
borkdude 2025-09-30T13:05:11.800579Z

it seems to re-use DOM elements when it can

Chris McCormick 2025-09-30T13:07:51.299219Z

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.

borkdude 2025-09-30T13:08:57.094669Z

lol

Chris McCormick 2025-09-30T13:09:41.551419Z

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

Chris McCormick 2025-09-30T13:10:52.254899Z

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.

borkdude 2025-09-30T13:11:18.116829Z

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
Chris McCormick 2025-09-30T13:12:41.164019Z

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?

borkdude 2025-09-30T13:12:57.574289Z

lol

borkdude 2025-09-30T13:13:10.051049Z

where do you see this?

Chris McCormick 2025-09-30T13:14:12.043939Z

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

borkdude 2025-09-30T13:14:20.026249Z

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

👍 1
👀 1
Chris McCormick 2025-09-30T13:14:24.625969Z

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

Chris McCormick 2025-09-30T13:14:44.432649Z

Will add to TODO.

borkdude 2025-09-30T13:15:13.137059Z

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

borkdude 2025-09-30T13:15:25.148989Z

it works well so far though

Chris McCormick 2025-09-30T13:16:09.076819Z

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

Chris McCormick 2025-09-30T13:16:25.726829Z

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

borkdude 2025-09-30T13:16:46.414619Z

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

Chris McCormick 2025-09-30T13:16:46.747769Z

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

Chris McCormick 2025-09-30T13:17:08.003899Z

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

Harold 2025-09-30T15:17:53.651129Z

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

2025-09-30T18:11:20.059199Z

I am horrifimazed by this entire journey

😆 1
borkdude 2025-09-30T18:11:43.078779Z

great description

borkdude 2025-09-30T18:12:34.535769Z

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
borkdude 2025-09-30T18:14:53.236829Z

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

2025-09-30T18:16:25.667839Z

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.

2025-09-30T18:17:09.874159Z

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

2025-09-30T18:17:16.525249Z

but I definitely get the fun side

borkdude 2025-09-30T18:17:59.319559Z

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
2025-09-30T18:18:13.222339Z

yeah if nothing else it skipped to proof of concept effort

2025-09-30T18:18:41.359599Z

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

2025-09-30T18:19:11.617609Z

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

borkdude 2025-09-30T18:20:40.155949Z

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

2025-09-30T18:29:16.412099Z

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.

2025-09-30T18:30:14.167899Z

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

☝️ 1
2025-09-30T18:33:20.042819Z

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
borkdude 2025-09-30T18:33:59.042959Z

That's really great to hear, thank you

Chris McCormick 2025-10-01T01:50:07.514249Z

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

borkdude 2025-09-29T17:49:03.204029Z

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

borkdude 2025-09-29T17:49:16.471399Z

(not quite sure how this works normally in reagent)

borkdude 2025-09-29T17:58:22.820079Z

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

🆒 1
🚀 2
borkdude 2025-09-30T08:53:05.440839Z

Build size analysis Gzipped it's < 10kb

🔥 1