hyperfiddle

Toyam Cox 2025-02-23T02:31:28.261509Z

I'm trying to port from v0.dev to electric. So far, I have it to the point where an AI can reliably give me the hiccup representation of this page that I'm trying to create. There are enough examples for AI to do that. However, then translating from hiccup into the functions that are called in electric UI is its own challenge. Anybody got a great mechanical program to run this translation? Mostly just needs props and dom/div and that would get me to where I can annotate with e/ tools

tobias 2025-02-23T06:12:58.708019Z

I don’t have a tool for this but if you want to write one then you might find hickory to be a useful intermediate format. It’s more verbose than hiccup but easier to parse because it’s explicit. The library also has the facility to read HTML into hiccup or hickory. https://github.com/clj-commons/hickory

πŸ™Œ 1
braai engineer 2025-02-23T12:28:06.165529Z

Why does my usage of Form! break? Seems like I should be able to pass a top-level element (dom/tr) like in the https://electric.hyperfiddle.net/tutorial/form_explainer:

(e/defn Entry [id account amount]
  (e/client
    (forms/Form!
      (dom/tr
        (e/amb
          (dom/td)
          (dom/td (Checkbox! false :entry/id id))
          (dom/td (dom/text (:account/ident account)))
          (dom/td (dom/text amount))
          (dom/td)))
      :commit (fn [dirty] [dirty nil]))))
does each element in e/amb have to return an Input thingy? I tried that too, but no change. Also tried Checkbox! vs Checkbox and Input! vs Input. If I take out Form! and e/amb and use Checkbox and Input, it works.

Dustin Getz (Hyperfiddle) 2025-02-23T13:21:09.650519Z

what is "break", please provide a complete issue report

Dustin Getz (Hyperfiddle) 2025-02-23T13:22:54.371709Z

your problem may be in the caller, i cannot say because you have not told me what the problem even is

braai engineer 2025-02-23T13:23:05.063699Z

making minimal example

braai engineer 2025-02-23T13:55:06.697849Z

OK, it seems that if I pass (dom/td (dom/text "some text")) as one of the e/amb arguments inside a Form!, it works, but if I pass an empty (dom/td), the form will render but on commit it throws "Cannot read properties of null (reading ... To reproduce, uncomment the empty (dom/td):

(e/defn Testing []
  (e/client
    (let [!state (atom {:checked? false :text ""})
          {:as state :keys [text checked?]} (e/watch !state)
          edits  (forms/Form!
                   (dom/tr
                     (e/amb
                       (dom/td (dom/text 123.45)) ; this works
                       ;(dom/td) ; this breaks if you uncomment it
                       (dom/td (Checkbox! :checked? checked?))
                       (dom/td (Input! :text text))))
                   :commit (fn [dirty]
                             (let [prediction nil]
                               [(merge state dirty) prediction]))
                   :debug true)]

      (e/for [edit edits]
        (let [[t data _guess] edit]
          (case (do
                  (reset! !state data)
                  true)
            (t))))

      (dom/text "State:" (pr-str state)))))

braai engineer 2025-02-23T13:57:23.820859Z

maybe because dom/text calls e/drain, whereas dom/td uses element*? (dom/td (dom/text)) seems to work for me.

braai engineer 2025-02-23T13:57:29.215829Z

^ @dustingetz example above.

Dustin Getz (Hyperfiddle) 2025-02-23T13:59:55.287619Z

please print (e/as-vec edits) so we can confirm there's an unwanted nil

braai engineer 2025-02-23T14:01:29.467109Z

If I insert a (prn 'edits (e/as-vec edits)) before the (e/for [edit edits] ...), it throws before it can print:

EXCEPTION
L851:13 electric-starter-app.main/Testing
function TypeError() { [native code] }: Cannot read properties of null (reading 'cljs$core$IFn$_invoke$arity$0')

Dustin Getz (Hyperfiddle) 2025-02-23T14:02:13.860399Z

comment out the e/for, the prn and the e/for are concurrent and racing, they're running in backwards order

braai engineer 2025-02-23T14:04:13.532269Z

I don't see an unexpected nil with the empty (dom/td) uncommented:

edits "[[#object[G__47080] {:checked? false, :text \"arstarst\"} nil]]"
(the nil at end is prediction = nil)

braai engineer 2025-02-23T14:04:26.698739Z

^ from: (prn 'edits (pr-str (e/as-vec edits)))

Dustin Getz (Hyperfiddle) 2025-02-23T14:07:06.831639Z

ok, it's not what i thought it was but i can reproduce the problem

πŸ‘ 1
Dustin Getz (Hyperfiddle) 2025-02-23T14:09:58.730459Z

workaround with (dom/dt (e/amb) #_(dom/text "str1"))

Dustin Getz (Hyperfiddle) 2025-02-23T14:11:25.387319Z

(dom/td) does return nil (because it puts children inside an implicit do and (do) evaluates to nil. We plan to change that to an implicit (e/amb) but it's potentially breaking and we aren't ready to do it yet

Dustin Getz (Hyperfiddle) 2025-02-23T14:11:42.107879Z

nonetheless, the Form! probably should not crash if a nil sneaks in

Dustin Getz (Hyperfiddle) 2025-02-23T14:27:59.831109Z

i also don't understand {0 nil} in the tutorial debug display, though the form is producing correct transactions

braai engineer 2025-02-23T14:55:32.509409Z

is Form! meant to work in an e/for? Not sure if the workaround applies here:

(e/defn FormThing []
  (e/client
    (dom/tr (dom/td (dom/text "test row")))
    ;; this works:
    (dom/tr
      (dom/td
        (forms/Form!
          (Input! :text "hi")
          :commit (fn [dirty] [dirty nil])
          :debug true)))
    ; but this doesn't:
    (forms/Form!
      (dom/tr (dom/td (Input! :text "hi")))
      :commit (fn [dirty] [dirty nil])
      :debug true)))

(e/defn Testing2 []
  (e/client
    (dom/div
      (dom/table
        (e/for [x (e/diff-by identity (range 3))]
          (FormThing))))))

Dustin Getz (Hyperfiddle) 2025-02-23T14:57:27.694649Z

i would appreciate if you just eliminate the word "works" "breaks" from your vocabulary and never say it again

braai engineer 2025-02-23T15:26:47.665609Z

so "works" here just means it renders (for me), and "breaks" means it doesn't render for me ...and it's because Form! mounts a <form> element under <table> where the <tr> should be and that doesn't show up in my browser, so it's not "broken," but it makes Form! tricky to use for the columns in a table row. Before reading the source for Form!, I had assumed Form! was a conceptual wrapper for inputs, not an actual DOM form element. Would you be less annoyed if I had said, "it's not showing"? I'll try to be precise but I don't think the animosity is warranted – it's not an attack πŸ€·β€β™‚οΈ

Dustin Getz (Hyperfiddle) 2025-02-23T15:28:38.074889Z

after reading this three times i think you are saying there is a css layout issue? Try display:contents on the <form>

braai engineer 2025-02-23T15:36:00.936729Z

unfortunately, display:contents has no effect because my browser has a user agent rule for any form under a table:

:-webkit-any(table, thead, tbody, tfoot, tr) > form:-internal-is-html {
    display: none !important;
}
...and I can't seem to override it, but display: table-row would be the right thing if the <form> element functioned as a <tr>. if I change the <form> element to <tr> in the console after rendering, it shows up.

Dustin Getz (Hyperfiddle) 2025-02-23T15:37:23.869649Z

don't use table then

braai engineer 2025-02-23T15:44:12.068089Z

yeah, I'll switch to divs and set display:table-row if I want that layout. it would be convenient if there was a logical (right word?) State! helper that dit not mount anything but just tracked state and handled commit/discard. what I'm really after is an easy way to collect all nested changes for components under an e/for, where each e/for element has its own row ID, so that I can collect the changes and bulk submit tem to the DB, i.e. only track changes and not inject any DOM elements, but I can probably bubble this up from the subcomponents.

Dustin Getz (Hyperfiddle) 2025-02-23T15:47:35.915179Z

i dont understand that, you're still making me work really hard to parse your meaning. i think you should spend more time working through the todo list examples in the tutorial

Dustin Getz (Hyperfiddle) 2025-02-23T15:47:58.069369Z

if you have a question, please ask a very clear question, it should end in a question mark

braai engineer 2025-02-23T16:12:08.534599Z

I revisited the todos example and it looks like Service is doing what I'm trying to describe: all the todo events (or effects?) seem to "bubble up" up and the (e/Apply Effect args) triggers the handler for each effect, create/edit/toggle, if I understand correctly? I need to spend more time with todos – there's a lot going on in there... πŸ™‚

πŸ’― 1
πŸ™ 1
Dustin Getz (Hyperfiddle) 2025-02-23T16:22:23.262649Z

yes the service handles requests concurrently, Form! has the ability to merge a set of individual effect requests (such as field level edits) into a single atomic effect request for the batch - this is what the :commit prop does

braai engineer 2025-02-23T17:12:10.995579Z

Do I understand correctly that in v3, for a component to return a stream of values to its caller (e.g. dom/On "click" events), a flow must appear in the tail position of the component, i.e. as the last expr? Or is there another way for events to flow up the chain I'm not aware of?

Dustin Getz (Hyperfiddle) 2025-02-23T17:23:34.278889Z

children values flow out of dom container elements via either implicit do or explict e/amb

πŸ‘ 1