Fork me on GitHub
#hyperfiddle
<
2023-09-12
>
joshcho06:09:24

How does one go about debugging something like this? I think I face something like this couple times a day, and I have no idea where to look.

------ ERROR -------------------------------------------------------------------
 File: /home/pollock/wakmantle/src/user.cljs:8:3
--------------------------------------------------------------------------------
   5 |    hyperfiddle.electric-dom2))
   6 |
   7 | (def electric-main
   8 |   (hyperfiddle.electric/boot ; Electric macroexpansion - Clojure to signals compiler
---------^----------------------------------------------------------------------
null
StackOverflowError:
	clojure.lang.RT.boundedLength (RT.java:1790)
	clojure.core/apply (core.clj:669)
	clojure.core/update-in/up--6922 (core.clj:6220)
	clojure.core/update-in (core.clj:6221)
	clojure.core/update-in (core.clj:6207)
	cljs.analyzer/register-constant!/fn--2398 (analyzer.cljc:537)
	clojure.lang.Atom.swap (Atom.java:37)
	clojure.core/swap! (core.clj:2369)
	clojure.core/swap! (core.clj:2362)

xificurC07:09:05

yeah, the error isn't very helpful πŸ™‚ Is this on hot code reload? Sometimes restarting the REPL produces a cleaner error (or makes it go away)

xificurC07:09:40

otherwise I'd just comment bisect

caleb.macdonaldblack07:09:43

When I experienced RangeError: Maximum call stack size exceeded in the browser. I also had shadow failing to compile with StackOverflowError at the same time as I was playing around trying to work out what was going on.

J07:09:06

I solve the problem with the -Xss option in the jvm-opts into my dev alias.

πŸ‘ 1
J07:09:26

Like this:

Dustin Getz10:09:28

compile time stackoverflow usually just means just increase comptime stack size (available to shadow), electric compilation requires more than 1MB stack

joshcho11:09:01

Yeah, I was actually just running out of memory I think, needed -Xss8m. Was using some large literals instead of an actual db for now.

πŸ‘€ 1
joshcho12:09:24

I sat down for 16 hours or sth and built a full electric app (https://semantle.com/ clone) in 800 LOC. Pretty weird to keep all the moving components in my head and "not lose any context" while doing it. The https://pages.cs.wisc.edu/~remzi/Naur.pdf never has to be reconstructed. Possible because of 1 file and less LOC. Also been accruing good macros along the way.

πŸ”₯ 6
clojure-spin 2
☺️ 1
xificurC13:09:49

are you planning to share the code?

joshcho14:09:42

https://github.com/joshcho/wakmantle, FYI it's in Korean tho and for a streamer fanbase

grounded_sage14:09:42

I know this is not the intention with Electric. But does it make sense and is it possible to easily decouple it from backend. I know people complain about the authoring experience but I actually kind of like it haha. I would build frontends entirely in electric dom functions. Actually looking at building a copy paste UI kit for electric and it would be super nice to be able to host it statically.

danielstockton14:09:55

Along the same lines, I have wondered how one might provide an API to users when building an app in electric. I guess there needs to be some kind of formal separation between the electric backend and main API. The electric backend would be a thin proxy.

xificurC15:09:41

@U05095F2K I parsed 2 questions: 1. can one write client-only code with electric? 2. can electric generate a static website? For 1 - you can write client-only electric code today. You still need the machinery (a server with a websocket to connect to) but there should be no other traffic than the initial connect. This is not a priority for us though, our goal is to enable building rich dynamic UIs For 2 - this is a common request that is out of scope for now. Electric is a reactive language, you'll be better off using a static site generator

xificurC15:09:23

@U0DHDNZR9 since electric builds on top of clojure(script) you can use the same tools you're used to. The UI can (and should!) communicate with the server directly, but if a third party needs to request data from the backend you can build e.g. an HTTP API alongside as usual

danielstockton15:09:03

Yep, i was talking more about ways to avoid repeating yourself between electric vackend code and code to serve an api alongside. I thought @U05095F2K was getting at something similar i.e. hiw to write reusable components that fetch data from unknown backend

xificurC15:09:40

> i was talking more about ways to avoid repeating yourself between electric vackend code and code to serve an api alongside typically the business logic is decoupled from the controller, right? If you have fetch-user-info the controller will call it and in electric you'd call it directly, without an HTTP endpoint

πŸ‘ 2
xificurC16:09:16

> how to write reusable components that fetch data from unknown backend How does an HTTP endpoint guard you from unknown backends? If you GET /fetch-user-info/11 any backend you connect to needs to provide that endpoint. In electric for (e/server (user-info/fetch-info 11)) you need a namespace aliased to user-info that has a function called fetch-info. The other option is to take data as arguments, in which case you'll have a data shape dependency

danielstockton16:09:54

You can supply an endpoint but yes you have a data shape dependency. Not saying that http guards you against unknown backends, but you can pick and choose which backend to use.

danielstockton16:09:53

In electric you would have an implicit contract to implement a certain function i.e. the same data shape dependency.

grounded_sage16:09:32

Okay I guess it can be hosted on free tier of a service or people can spin up themselves to see it.

grounded_sage16:09:33

I'm basically looking to replicate https://ui.shadcn.com/

grounded_sage16:09:01

When I pass in something other than a string for Collapsible it doesn't render the button. Is what I am writing expected to be valid?

(e/defn Collapsible [{:keys [trigger content disabled? default-open?]}]
  (let [!open? (atom default-open? true false)
        open? (e/watch !open?)]
    (dom/button
      (dom/props {:type "button"
                  :aria-expanded open?
                  :disabled disabled?
                  :data-state open?
                  :data-disabled disabled?})
      (dom/on "click" (e/fn [_] (reset! !open? (not @!open?))))
      (if (string? trigger)
        (dom/text trigger)
        trigger))
    (dom/div
      (dom/props {:hidden (not open?)})
      (if (string? content)
        (dom/p (dom/text content))
        (content)))))

    (dom/div
      (dom/h1 (dom/text "Collapsible"))
      (dom/p (dom/text "An interactive component which expands/collapses a panel."))
      (dom/div (dom/props {:class  "flex items-center justify-between space-x-4 rounded-md p-4 border"})
        (dom/h4 
          (dom/props {:class "text-sm font-semibold"})
          (dom/text "@peduarte starred 3 repositories"))
        (Collapsible. {:trigger (dom/div
                                  (dom/img (dom/props {:src "./radix-icons/chevron-up.svg"}))
                                  (dom/img (dom/props {:src "./radix-icons/chevron-down.svg"})))
                       :content "Yes. Free to use for personal and commercial projects. No attribution
    required."})))

grounded_sage18:09:40

thought I would do something simple to take a break from hard stuff but I somehow feel this moves into macro territory??

xificurC18:09:26

the function you pasted takes a map as its argument, what do you mean you're passing in a string?

xificurC18:09:12

(reset! !open? (not @!open?)) is (swap! !open? not)

xificurC18:09:28

what semantics are you trying to capture in

(if (string? trigger)
        (dom/text trigger)
        trigger))

xificurC18:09:06

in

(if (string? content)
        (dom/p (dom/text content))
        (content)))))
content will be a string or a clojure function?

grounded_sage18:09:27

Yea. Maybe I am going about this wrong. I was just thinking for something simple it would be a string and optionally take a electric dom function when people want to create something more custom

grounded_sage18:09:18

Didn’t know could do swap not. Been writing code like this for ages πŸ™ˆ

xificurC18:09:37

I see. Electric functions need a new or dot syntax - (new content) or (content.). We typically uppercase them to remind ourselves

xificurC18:09:19

our error handling and reporting is something to work on. In the meantime when something doesn't work the simplest approach is comment bisecting

grounded_sage18:09:29

I was doing printlns but getting nil. Also the button completely vanishes which is wrapping the trigger parameter. Just pulling up laptop to quickly try what you have shared

telekid18:09:33

not related to the conversation at hand, but note that (swap! !open? not) is atomic while (reset! !open? (not @!open?)) is not. In general, if notice yourself referencing your atom directly in the second argument of either swap! or reset, you're opening yourself up for a race condition.

πŸ™ 1
grounded_sage19:09:53

Yea so when I pass in (dom/div …) in the map assoc’d to the :trigger the button inside of Collapsible disappears. Which I would expect to not be affected.

grounded_sage19:09:24

Even when I wrap the if statement in another div

xificurC19:09:10

can you share the call? (Collapsible. {:trigger (dom/div ..)}) is not what you'd expect

grounded_sage21:09:38

I'm not sure if this is meaningful to work on and if there is prior work in the Clojure space replicating the work in Radix.

grounded_sage08:09:15

Yea that let binding is me experimenting trying to figure out what is going on.

grounded_sage10:09:36

nvm. I got news that a UI toolkit is being worked on. I'll stick to my Datahike integration and LLM work

Tommy Jolly18:09:38

Just coming back to Electric after a long break, so it's possible I've made a stupid error here, but -- Why does this not work as I expect?

(e/defn Div [x]
  (dom/div x))

;; later, in an (e/defn ,,, (e/client ,,,))
(Div. (dom/text "hello"))
I expect <div>hello</div>, of course, but it's outputting the rather puzzling
hello
<div></div>

xificurC18:09:58

dom/text performs an effect of mounting a text node under the current dom node, which is not the div you think it is. Compare to clojure code (foo (println :bar)), the println runs before foo

xificurC18:09:10

(dom/div (dom/text "hello")) works because dom/div is a macro that expands to code that first mounts a new div, sets it as the current one, then runs the body

Tommy Jolly18:09:30

Ah I see, I assumed it would return a DAG. So dom/text acts on dom/node?

Tommy Jolly18:09:47

Oh, and I see dom/element does the same, so essentially I can't pass around pieces of dom like this. OK!

xificurC18:09:52

to write in this style you need macros. Another approach is to thunk the effect

(e/defn Div [X]
  (dom/div (new X)))

(Div. (e/fn [] (dom/text "hello")))
which in clojure would correspond to (foo #(println :bar))

πŸ‘ 2
Dustin Getz20:09:15

> I assumed it would return a DAG e/fn in fact constructs a "DAG as a value" to be passed around and then (new F) splices it in

πŸ‘οΈ 1
J20:09:26

Hi guys! Does the do run his body in parallel?

Dustin Getz20:09:23

yes, there are a few discussions if you search

Dustin Getz20:09:54

(do (dom/div x) (dom/div)) will render left to right, but if x updates it will update the middle div without touching the final div

Dustin Getz20:09:22

which implies a more advanced scenario:

Dustin Getz20:09:11

(do (dom/div (Slow-query. x)) (dom/div (Slow-query. y))) –– the queries run in parallel based on causal dependency on x and y

Dustin Getz20:09:16

note that it's backwards compatible with clojure, except exception semantics

Dustin Getz20:09:33

if the two queries each throw but different exceptions, you'll see surprising things that in edge cases are not backwards compatible

πŸ‘ 1
J20:09:17

what do you mean by backwards compatible?

Dustin Getz20:09:39

clojure compatible means if you take a pre-existing clojure library like core.match, and use it inside e/defn, it is guaranteed to work

Dustin Getz20:09:11

we cannot quite provide that guarantee in the case of exceptions, but in practice, core.match does indeed work

J20:09:36

Ok got it.

joshcho19:09:20

is there a way to run a sequence of expressions sequentially?

Dustin Getz19:09:42

what are you trying to accomplish?

joshcho23:09:31

i have come across situations where i need to call client A, server code B, then client code C. or in general enforce some order to my expressions.

Dustin Getz23:09:47

we would like to see it in context to see if we can find a better way

Dustin Getz23:09:21

it is possible to sequence today but we don’t commit to keeping that capability

joshcho23:09:03

can't think of any atm, but i'll ping back once that situation occurs

πŸ‘ 1
grounded_sage20:09:56

Gratitude to the core team working on electric for their work and answering all our questions.

gratitude 10
βž• 8
πŸ™‚ 6