hyperfiddle 2025-05-20

I'm having trouble using pprint and with-out-str. Example: (dom/p (dom/text (with-out-str (pprint {:a 1})))) results in error: [clojure.core/*out*] is not an electric var

Workaround for now:

#?(:cljs
    (:require [goog.string.StringBuffer]
              [cljs.pprint :refer [pprint]]))


(defn pprint-str [x]
  (let [sb (goog.string.StringBuffer.)]
    (binding [*print-newline* true
              *print-fn* (fn [s] (.append sb s))]
      (pprint x))
    (str sb)))

Dustin Getz (Hyperfiddle) 2025-05-20T11:47:56.521509Z

yeah clojure bindings are not Electric-compatible, you need to wrap

👍 1

I want to use hyperfiddle.electric-forms5, but add translations to commit/discard buttons. What's a good way to do this? I don't want to change the forms5 file, as I expect new versions to roll out. Do I just hack it with JS, (set! (.-textContent commit-btn) "Commit")?

Dustin Getz (Hyperfiddle) 2025-05-20T13:53:30.840409Z

it may be possible to do override in pure css, but yes hacks are acceptable IMO

Dustin Getz (Hyperfiddle) 2025-05-20T13:54:24.712849Z

i will note the internationalization requirement

Dustin Getz (Hyperfiddle) 2025-05-20T13:56:12.539069Z

electric-forms is not in as good state as we want it to be because we had to switch workstreams for a few months – mostly we direct our roadmap based on what our customers need

Ok, thanks for a fast answer

This should work today. You can customize “Submit” and “Cancel”.

(ns ...
  (:require [hyperfiddle.electric-forms5 :refer [Form! Input! SubmitButton! DiscardButton!]]))

(Form! {::message "hello"}
  (e/fn [{:keys [::message]}]
    (e/amb
      (Input! ::message message)
      (SubmitButton! :label "Submit")
      (DiscardButton! :label "Cancel"))))

🙏 1
Dustin Getz (Hyperfiddle) 2025-05-20T13:56:59.808029Z

Reminder, I'm speaking at ReClojure (London) next week. The topic will be process supervision, differential dataflow, and user interfaces.

👍 1
🤘 10

I'm having trouble with the starter-app. I renamed all (afaict) the references to electric-starter-app with my directory name, but it seems the main.js file that shadow-cljs builds is corrupted. I can't open it, with emacs/`cat` /`head`

(base) baruchberger@192 bb % clj -A:dev -X dev/-main 
INFO  dev: Starting Electric compiler and server...
INFO  io.undertow: starting server: Undertow - 2.3.10.Final
INFO  org.xnio: XNIO version 3.8.8.Final
INFO  org.xnio.nio: XNIO NIO Implementation Version 3.8.8.Final
INFO  org.jboss.threads: JBoss Threads version 3.5.0.Final
shadow-cljs - server version: 2.26.2 running at 
shadow-cljs - nREPL server started on port 51774
[:dev] Configuring build.
[:dev] Compiling ...
Compiling ...
[:dev] Build completed. (200 files, 1 compiled, 0 warnings, 1,35s)
INFO  dev: 👉 
WARN  org.eclipse.jetty.server.HttpChannel: handleException /js/main.js java.io.FileNotFoundException: /Users/baruchberger/bb/resources/public/bb/js/main.js (Operation not permitted) 

Yeah for dev I tried to create a docker container for dev mode, manually curled the token to make it build, but couldn't yet open the server then ran out of time (or rather, got excited by the claude 4 release).

This is what bitdefender has in the history. It includes attempts I made to clear the xattr macOS file metadata and putting the files into different directories etc.

GT-JS Acsogenixx.284.CEC83A89 resident access to the file has been denied /Users/baruchberger/electric3-starter-app/resources/public/electric...

Dustin Getz (Hyperfiddle) 2025-05-23T11:01:21.805439Z

does it say why it flagged the file?

Dustin Getz (Hyperfiddle) 2025-05-23T11:01:31.896249Z

do other clojurescript projects build?

The why would be that acsogenixx detection. More information than this is not given sadly.

👀 1

Yes other projects build, it’s the first time I run into trouble like this.

Dustin Getz (Hyperfiddle) 2025-05-23T11:07:19.187039Z

Can you please upload the flagged file to https://www.virustotal.com/gui/home/upload and see what it says

Dustin Getz (Hyperfiddle) 2025-05-23T11:07:37.066649Z

to determine if this is a false positive based on how many engines flag it

Dustin Getz (Hyperfiddle) 2025-05-23T11:08:09.567369Z

GT-JS means generic trojan - javascript, it is not a specific signature match

Dustin Getz (Hyperfiddle) 2025-05-23T11:08:46.865879Z

also can you upload it here as an attachment, i am going to compare to results on my machine

Dustin Getz (Hyperfiddle) 2025-05-23T11:09:45.980369Z

what's weird is that our js asset does not contain auth code or servers or anything weird it's just another cljs app, so my first question is - maybe your system is compromised?

I also tried to send the file here but slack won't let me.

Dustin Getz (Hyperfiddle) 2025-05-23T11:19:12.119569Z

due to virus detection?

I don't know, could be big slack server restrictions, I would try to just send a text snippet but can't view the text in any program that lets me copy 😅

I can see it in the mac finder preview though, just seems like a regular shadow-cljs built main file.

Dustin Getz (Hyperfiddle) 2025-05-23T11:20:40.501419Z

drag drop it into slack as a file to use the attachment feature

I did, but that blocked it.

I guess my filesystem won't let it.

Dustin Getz (Hyperfiddle) 2025-05-23T11:21:06.124859Z

how about a gist

Dustin Getz (Hyperfiddle) 2025-05-23T11:22:08.297949Z

oh bitdefender is blocking you from viewing the content

I can view it in the finder however, I could transcribe it with ocr but it's 55kb and a small window.

The virus total thing has a behaviour tab which is interesting. It says it's creating files? C:\Windows\ServiceProfiles\LocalService\AppData\Local\FontCache\Fonts\Download-1.tmp

Dustin Getz (Hyperfiddle) 2025-05-23T11:26:24.830679Z

it does not look good that's for sure

Dustin Getz (Hyperfiddle) 2025-05-23T11:27:43.884449Z

but i don't see how electric-starter-app can be compromised, the file came from shadow. the most likely thing based on available evidence is that something on your system appended malicious js to the end

Dustin Getz (Hyperfiddle) 2025-05-23T11:28:12.477699Z

i still need to see if a main.js from my system produces the same alert

Okay, I'm trying to OCR transcribe the whole file

somehow finder of macOS has a preview window I can scroll in

Dustin Getz (Hyperfiddle) 2025-05-23T11:28:56.410079Z

lol

Dustin Getz (Hyperfiddle) 2025-05-23T11:29:21.093059Z

I thought you said you were on a mac mini btw?

Dustin Getz (Hyperfiddle) 2025-05-23T11:29:42.321289Z

oh, that came from the scanner tool's sandbox test machine

Hmm, I transcribed the whole thing with chatGPT which was laborious, but uploading that reconstructed file doesn't make any alarm bells go off with totalvirus.

👀 1

Can you reproduce? Because I just tried on my laptop where I was playing with electric on earlier and that also has it.

Seems like it's a false positive. But why does that not happen for other bundles. 😕

Dustin Getz (Hyperfiddle) 2025-05-23T15:23:23.709369Z

i have not attempted to repro, but i did review the file and agree i do not see malicious code ... but how does it do that stuff in the test sandbox?

Dustin Getz (Hyperfiddle) 2025-05-23T15:24:08.585969Z

is this the actual file or an OCR?

This is the actual file (download and see it fail the virustotal). I also don't know why that does that stuff in the test sandbox.

If I were to be infected and this only happens for my machines then this would be a super advanced threat, and somewhat strange to be targeting windows from a macos machine.

Dustin Getz (Hyperfiddle) 2025-05-23T15:27:13.732059Z

the windows targetting is in the secondary payload, the trojan bootstraps by just connecting to a control server and downloading the next stage

Ah that would make sense.

should I spin up a cloud machine to see if I can reproduce?

Dustin Getz (Hyperfiddle) 2025-05-23T15:31:31.654959Z

nah

Dustin Getz (Hyperfiddle) 2025-05-23T15:32:03.905499Z

there doesn't seem to be a threat here, i will continue to investigate when i have time (likely backlog until we get another report as we are traveling to reclojure tomorrow)

Dustin Getz (Hyperfiddle) 2025-05-23T15:32:25.191409Z

i am just "confused" 🙂

Alright, thanks for having a look anyway. Safe travels to ReClojure!

Dustin Getz (Hyperfiddle) 2025-05-23T15:32:50.936199Z

ty for the reports and diagnosing

Dustin Getz (Hyperfiddle) 2025-05-20T22:03:23.576579Z

could there be a unix permissions issue? e.g. you accidentally cloned with root or something?

Things I've tried: • removing .cpcache .shadow-cljs and trying again • doing all with sudo • seeing if there's anything special about my filesystem, can't find anything, other files in the resources dir open

Dustin Getz (Hyperfiddle) 2025-05-20T22:03:38.621759Z

try fresh clone in new directory

I've also tried that, but I'll try it with nothing renamed and (meaning electric-starter-app -> something else)

(base) baruchberger@192 ~ % git clone 
Cloning into 'electric3-starter-app'...
remote: Enumerating objects: 305, done.
remote: Counting objects: 100% (197/197), done.
remote: Compressing objects: 100% (128/128), done.
remote: Total 305 (delta 81), reused 92 (delta 36), pack-reused 108 (from 1)
Receiving objects: 100% (305/305), 262.36 KiB | 18.74 MiB/s, done.
Resolving deltas: 100% (108/108), done.
(base) baruchberger@192 ~ % cd electric3-starter-app 
(base) baruchberger@192 electric3-starter-app % 
(base) baruchberger@192 electric3-starter-app % clj -A:dev -X dev/-main                                      
Downloading: com/hyperfiddle/electric/v3-alpha-SNAPSHOT/maven-metadata.xml from clojars
Downloading: com/hyperfiddle/hyperfiddle-contrib/v0-alpha-SNAPSHOT/maven-metadata.xml from clojars
Downloading: com/hyperfiddle/hyperfiddle-contrib/v0-alpha-SNAPSHOT/maven-metadata.xml from clojars
Downloading: com/hyperfiddle/electric/v3-alpha-SNAPSHOT/maven-metadata.xml from clojars
INFO  dev: Starting Electric compiler and server...
INFO  io.undertow: starting server: Undertow - 2.3.10.Final
INFO  org.xnio: XNIO version 3.8.8.Final
INFO  org.xnio.nio: XNIO NIO Implementation Version 3.8.8.Final
INFO  org.jboss.threads: JBoss Threads version 3.5.0.Final
shadow-cljs - server version: 2.26.2 running at 
shadow-cljs - nREPL server started on port 54108
[:dev] Configuring build.
[:dev] Compiling ...
Please sign up or login to activate:  
Compiling ...
:hyperfiddle.electric.shadow-cljs.hooks3/recompile-clj hyperfiddle.electric3
:hyperfiddle.electric.shadow-cljs.hooks3/recompile-clj hyperfiddle.electric-css3
:hyperfiddle.electric.shadow-cljs.hooks3/recompile-clj hyperfiddle.electric-dom3-props
:hyperfiddle.electric.shadow-cljs.hooks3/recompile-clj hyperfiddle.electric-dom3
:hyperfiddle.electric.shadow-cljs.hooks3/recompile-clj electric-starter-app.main
[:dev] Build completed. (200 files, 199 compiled, 0 warnings, 12,61s)
INFO  dev: 👉 
WARN  org.eclipse.jetty.server.HttpChannel: handleException /js/main.js java.io.FileNotFoundException: /Users/baruchberger/electric3-starter-app/resources/public/electric_starter_app/js/main.js (Operation not permitted)
WARN  org.eclipse.jetty.server.HttpChannel: handleException /js/main.js java.io.FileNotFoundException: /Users/baruchberger/electric3-starter-app/resources/public/electric_starter_app/js/main.js (Operation not permitted)
Same problem

(base) baruchberger@192 electric3-starter-app % cat resources/public/electric_starter_app/index.css
@import url('');

* {
    box-sizing: border-box;
}

body {
    font-family: 'Open Sans', Arial, Verdana, sans-serif;
    background-color: rgb(248 250 252);
}
(base) baruchberger@192 electric3-starter-app % cat resources/public/electric_starter_app/js/main.js 
cat: resources/public/electric_starter_app/js/main.js: Operation not permitted

Dustin Getz (Hyperfiddle) 2025-05-20T22:07:13.037909Z

it looks like a system permission issue to me, MacOS has some weird security stuff happening in certain folders that i've seen cause issues like this

Hmm yes, but why only for the main file.

👀 1

(base) baruchberger@192 electric3-starter-app % head -n 5 resources/public/electric_starter_app/js/cljs-runtime/electric_starter_app.main.js
goog.provide('electric_starter_app.main');
electric_starter_app.main.Main = (function() {
var electric_starter_app$main$Main = null;
var electric_starter_app$main$Main__0 = (function (){
return cljs.core.PersistentHashMap.fromArrays([1],[hyperfiddle.electric.impl.runtime3.ctor(new cljs.core.Keyword("electric-starter-app.main","Main","electric-starter-app.main/Main",1906897109),(0))]);

Dustin Getz (Hyperfiddle) 2025-05-20T22:08:11.753019Z

ls -al ?

Dustin Getz (Hyperfiddle) 2025-05-20T22:08:29.249619Z

are you in a container?

(base) baruchberger@192 electric3-starter-app % ls -al resources/public/electric_starter_app/js 
total 128
drwxr-xr-x@   5 baruchberger  staff    160 May 21 00:05 .
drwxr-xr-x    6 baruchberger  staff    192 May 21 00:05 ..
drwxr-xr-x@ 403 baruchberger  staff  12896 May 21 00:05 cljs-runtime
-rw-r--r--@   1 baruchberger  staff  55103 May 21 00:05 main.js
-rw-r--r--@   1 baruchberger  staff   6037 May 21 00:05 manifest.edn
Not in a container

Dustin Getz (Hyperfiddle) 2025-05-20T22:09:14.480479Z

did it ever work previously?

Hmm, I've used the starter app a couple of times on my macbook a week or 2 back.

This is on my mac mini, I believe I've run electric 2 on it a long time ago. Not sure.

was the first electric thing I did, a week or 2/3 back, streaming LLM responses into a https://milkdown.dev/docs/guide/using-crepe editor. Challenging to get to know both electric and electric JS interop, but pretty results quick.

It seems this is caused by the licensing strategy electric v3 has. Interestingly grep can inspect the file, but cat can't. I think the code responsible for writing the main.js file might be in the dev hook?

Dustin Getz (Hyperfiddle) 2025-05-21T10:41:06.695809Z

but you said it works on your other computer?

But that was with a slightly older electric-starter-app codebase iirc

Dustin Getz (Hyperfiddle) 2025-05-21T10:45:47.512459Z

main.js is written by shadow, not us. electric hot code reloading is implemented as a shadow hook but the actual compilation is performed by shadow

Dustin Getz (Hyperfiddle) 2025-05-21T10:48:34.148499Z

like thomas, i think you have a system issue, i think you should try on another computer. you can also clone inside a docker container if you don't have access to another computer. many people (100s) have successfully ran the starter app

Dustin Getz (Hyperfiddle) 2025-05-21T10:49:45.313449Z

the purpose of the shadow hook is to reload the clj namespaces when shadow reloads cljs namespaces, otherwise your electric app will desync

Okay, I don't know what I could have done to my system that is out of order. But I'll try the container version, thanks!

Dustin Getz (Hyperfiddle) 2025-05-21T10:51:48.184199Z

as thomas and I both said, macos has insane and obtuse security mechanisms on the file system

Dustin Getz (Hyperfiddle) 2025-05-21T10:54:17.951849Z

they have also changed over the years, is your mac mini up to date?

Yeah, I believe you. Tried a bunch of suggestions wrt mac file protections and they all don't work out or show what's wrong. If even LLM's are stumped I'm sure it's obtuse.

Yeah it's 15.5

👀 1

Found the culprit, BitDefender.

👀 1

Works in docker though, but that's not the dev version. Trying to get that to work by adjusting the dockerfile.

Dustin Getz (Hyperfiddle) 2025-05-22T23:00:26.035939Z

by try in docker, i meant use a generic image, apt-get install git, apt-get install clojure, git clone

Dustin Getz (Hyperfiddle) 2025-05-22T23:00:51.730319Z

Do you have any additional info about the BitDefender issue? I want to make a note of it

Can anyone help with a standard reactive query against Rama for V.3? This is the error I'm getting:

clojure.lang.ExceptionInfo: Value: missionary.core$relieve$fn__11209@1c7129bf {:v #function[missionary.core/relieve/fn--11209], :hyperfiddle.electric.impl.runtime3/unserializable true}
Using this code:
(defn proxy-callback [f]
  (fn [new _diff _old]
    (f new)))

(defn make-reactive-query [path pstate]
  (->> (m/observe
         (fn [!]
           (! nil)
           (let [cp (r/foreign-proxy-async path pstate {:callback-fn (proxy-callback !)})]
             #(.close @cp))))
    (m/relieve {})))
Which comes from this great project: https://github.com/jeans11/demo-rama-electric, which is now 2 years old.

I tried the three things you mentioned, but this was always the result:

ETL-ing Item
{:item-id "CARROTSALAD00000",
 :org-id "164J0XYOR0OP335J",
 :neat-item
 {:id "CARROTSALAD00000",
  :price 239,
  :description "Bombay Carrot Salad",
  :kind :item.kind/product,
  :heading-id "SALADS0000000000"}}
{:new 4,
 :_diff
 #object[com.rpl.rama.diffs.NewValueDiff 0x764edee0 "NewValueDiff[[{:id \"CARROTSALAD00000\", :price 239, :description \"Bombay Carrot Salad\", :heading-id \"SALADS0000000000\", :kind :item.kind/product} {:id \"FALAFELSALAD0000\", :price 420, :description \"Falafel Salad\", :heading-id \"SALADS0000000000\", :kind :item.kind/product} {:id \"GARDENSALAD00000\", :price 235, :description \"Acme Garden Salad\", :heading-id \"SALADS0000000000\", :kind :item.kind/product} {:id \"SALADS0000000000\", :price nil, :description \"Salads\", :heading-id nil, :kind :item.kind/heading}]]"],
 :_old 4}
One only is being put into the *item-depot and thus gets ETL-ed into $$org->items and yet the diff is a NewValueDiff of all 4 dishes.

This is what happens at the log "ETL-ing Item":

(r/local-transform> [(path/keypath *org-id *item-id)
                         #_(path/submap (keys *neat-item))
                         (m->multi-path *neat-item)
                         #_(path/termval *neat-item)] $$org->items)
It shows 2 of the suggestions being tried.

This is the proxy path:

[(path/keypath id) (path/subselect path/MAP-VALS (path/submap [:id :price :description :heading-id :kind]))]

I have this working in my project but I am away from a computer atm. I'll post a snippet when I'm back in an hour or so

1

Ok, this is what I am currently using. It's pretty similar to the old one, but I don't give it an initial value of nil.

(defn proxy-flow
  "Usage: (e/join (e/flow->incseq (proxy-flow (keypath key) pstate)))"
  [path pstate]
  #?(:clj
     (-> (m/observe
           (fn [!]
             (let [cp-ps (r/foreign-proxy-async path pstate
                           {:callback-fn
                            (fn [new _diff _old]
                              (! new))})]
               (fn cleanup [arg]
                 (r/close! @cp-ps)))))
         (m/relieve))))
And this is how I use it (`deps/pstates` is a map of pstates declared with e/declare and dynamically bound in electric at app startup)
(e/defn ReactiveProxy [path pstate-name]
  (e/server
    (e/join (e/flow->incseq (proxy-flow path (get deps/pstates pstate-name))))))
The trick for me with v3 was discovering e/flow-incseq and learning about incseqs. I kind of had this working with e/input and a nil initial value, but I wanted to get rid of the nil initial value if the thing it's proxying is never nil. Feedback from the electric team is very welcome on the approach here as I've only gleaned info on incseq's from this slack

Dustin Getz (Hyperfiddle) 2025-05-21T11:03:10.269189Z

@cjmurphy i think your error is caused by the call site

The unserializable is almost certainly the middle argument of the callback function, diff, which is an object that won’t serialize without additional work.

If you emit new, as in Jed’s example, the error should disappear. In both cases, I would recommend closing the proxy with a .thenComposeAsyncor .whenCompleteAsync or similar to avoid locking up any threads in case it doesn’t deref in a timely manner.

I had a few things to sort out (getting away from files being .clj to .cljc, where I'm comfortable that Electric and Missionary are comfortable, then using Rama's subselect on end of the path to be returning a vector rather than one element). I believe not using the e/join was my main problem, and the cause of the serialisation error message. I'm now using @noonian’s way exactly, so can confirm that works for me. Thanks also Dustin and @henrik - I'll get to trying out closing the proxy a bit later...

The domain is products per organisation (say menu of a restaurant). The problem I ended up with is that every time a product is added all products are broadcast! Claude suggested this was the problem: ResyncDiff happens when query paths are too complex for incremental diffing.

Anyway that's a Rama issue...

That’s probably Claude hallucinating. I don’t know how complex your paths are, but I’ve never run into that problem. I have run into the problem when there’s been other issues however: the connection to Rama is interrupted (eg. because of network issues). Since diffs are pushed straight through from write, it’s not inconceivable that it could occur if you’re doing coarse grained transforms on the pstate. Also, I don’t think Electric will care if you’re only returning new either way. By the way, it’s probably a good idea to guard the emit with (when-not (instance? DestroyedDiff diff) …).

I've made the path this simple: [(path/keypath id) (path/subselect path/MAP-VALS)] . It used to be this [(path/keypath id) (path/subselect path/MAP-VALS (path/view transform-item))] but I'm doing the transform on the browser client now. I agree on the hallucinating - the docs seem confident any path will work. I'm getting more than ResyncDiff. Now seeing NewValueDiff. But the problem with it is it contains that whole menu, when all I'm doing from the UI is changing the price of one dish. And I can see one depot event and I'm not sure what could be wrong with the ETL.

When you write the map, are you updating the entire map in place? NewValueDiff suggests that it’s been written wholesale, rather than manipulated with eg. submap.

I'm not using submap no. This is the ETL update:

(r/local-transform> [(path/keypath *org-id *item-id) (path/termval *neat-item)] $$org->items)

And this is $$org->items:

(r/declare-pstate s $$org->items
      (r/map-schema String
        (r/map-schema String
          (r/fixed-keys-schema
            {:id String
             :price Long
             :description String
             :kind clojure.lang.Keyword
             :heading-id String})
          {:subindex? true})))

👍 1

Well, if you’re termvaling an entire item, rather than writing eg. the price only with say (keypath *org-id *item-id :price), that would explain why you’re seeing NewValueDiffs at the leaf nodes, but not for the entire thing, since you are walking down via specific keys.

Rama does no diffing; the diffs emitted are entirely based on the semantics of the transform that updated them.

Apart from looking at the granularity of your updates, try being more specific in your proxy path, eg.: [(path/keypath id) (path/subselect path/MAP-VALS (submap [:id :price …])] and see what happens. I don’t know for sure that this makes a difference, but it might.

👍 1

I'll do a more detailed intro-item that looks at every key and only changes the minimum then, and see the results. Thanks for your input here.

👍 1

You can allow updating arbitrary parts of the map with some transform like (submap (keys *neat-item)) (termval *neat-item). This would allow *neat-item to contain only a subset of fields in the map and leave the rest of the fields alone. If that’s still too coarse, you can unfold it into a multi-path:

(defn m->multi-path
  [m]
  (apply multi-path (mapv (fn [[k v]] [(keypath k) (termval v)]) m)))
Though I’m not sure if this produces finer-grained diffs than submap.