Fork me on GitHub
#clojurescript
<
2016-10-31
>
mikeb03:10:51

Can anyone point me to any good example of client side routing and rum?

thedavidmeister04:10:10

not sure if i should post this in here or in the clojure chat

thedavidmeister04:10:27

i wrote a script that looks for namespaces using clojure.tools.namespace.find

thedavidmeister04:10:57

it works fine with .cljc files until i try to use #?( syntax in the ns at the top

thedavidmeister04:10:19

then the namespace mysteriously disappears from what clojure.tools.namespace.find/find-namespaces-in-dir can see

thedavidmeister04:10:14

anyone seen any behaviour like this at all?

iku00088804:10:30

@thedavidmeister Is it possible to post the ns form under discussion? Heard similar issues in the ns form.

thedavidmeister05:10:59

@iku000888 yup, i’ll put something up in a sec

kzeidler05:10:21

I'm refactoring an app I wrote about a year ago (my first web app !) to use the reagent atom instead of cljs.core/atom, and hopefully clean up a lot of the state management logic.

kzeidler05:10:03

I guess I'm a little unclear on how to accomplish my goal: ideally I'd like the input atom to result in a stack of inputs. Is the "correct" way to do this in reagent to keep the stack in a separate component?

thedavidmeister05:10:29

(ns styles.card
  (:require [garden.units :as u]
            [styles.colours]
            [styles.spacing]))
            
#?(:cljs (require '[garden.def :refer-macros [defstyles defrule]]))
#?(:clj (require '[garden.def :refer [defstyles defrule]]))
@iku000888

thedavidmeister05:10:57

cljs didn’t like that WARNING: Use of undeclared Var styles.card/require at line 6

thedavidmeister05:10:53

(ns styles.card
  (:require [garden.units :as u]
            [styles.colours]
            [styles.spacing]
    #?(:clj [garden.def :refer [defstyles defrule]]))
  #?(:cljs (:require-macros [garden.def :refer [defstyles defrule]])))

thedavidmeister05:10:05

i don’t get any more errors

thedavidmeister05:10:29

styles.card no longer appears in the list from clojure.tools.namespace.find/find-namespaces-in-dir

iku00088805:10:22

Not too familiar with reader conditionals, but seems like you need to wrap and then splice the clj branch

iku00088805:10:48

Plus I think you still need to require the namespace for cljs? (Crossing my fingers here...)

thedavidmeister05:10:49

@iku000888 i can use defstyles in this file and then prn the result of that in the browser

thedavidmeister05:10:45

so at least the cljs side of things seems to be working ok

thedavidmeister05:10:39

actually i can also require styles.card in clj and prn the result of defstyles there too

thedavidmeister05:10:02

so i can use the macros in styles.card just fine in both clj and cljs

thedavidmeister06:10:20

it’s just that when i run

thedavidmeister06:10:22

(prn (clojure.tools.namespace.find/find-namespaces-in-dir ( "src/cljc")))

thedavidmeister06:10:33

every namespace except styles.card appears

iku00088806:10:01

I would then try to debug the ns form with (read-string {:read-cond :allow} "ns form as string" ) in both cljs and clj

iku00088806:10:37

To see how the reader conditionals turn out.

thedavidmeister06:10:23

oh ok, i haven’t done that before

thedavidmeister06:10:38

gotta duck out for a bit

thedavidmeister06:10:42

thanks for the tip though

thedavidmeister06:10:51

i’ll let you know how i go 🙂

iku00088806:10:53

Good luck 👍

urbanslug07:10:19

Hey, I wanna test some cljc code but I think I have the structure wrong. For example this simple test fails

(ns ns-test
  (:require-macros [cljs.test :refer (is deftest async)]
                   [cljs.core.async.macros :refer [go]])
  (:require [cljs.test :as test]
            [cljs.core.async :refer [<!]]))
(defn adder
  []
  (go (+ 2 3)))

(deftest test-get-stats
  (async done
         (go (is (= (<! (adder)) 5))
             (done))))

urbanslug07:10:25

Oh man will you people please wake up?

iku00088807:10:47

Perhaps it would help to post what kind of error you get?

urbanslug08:10:57

No error, just says test failed

(ns milia.api.submissions-test
  (:require-macros [cljs.test :refer (is deftest async)]
                   [cljs.core.async.macros :refer [go]])
  (:require
   [cljs.test :as test]
   [cljs.core.async :refer [<! >! chan take!]]))

(defn test-async
  "Asynchronous test awaiting ch to produce a value or close."
  [ch]
  (async done
         (take! ch (fn [_] (done)))))

(deftest test1
  (let [ch (chan)]
    (go (>! ch "Hello"))
    (test-async
     (go (is (= "Hello" (<! ch)))))))

iku00088808:10:49

Not familiar with core.async, but [cljs.test :refer (is deftest async)] should be [cljs.test :refer [is deftest async]] ?

iku00088808:10:14

Sorry for not being able to help directly 😞

urbanslug08:10:52

iku000888 I don’t see the difference in what you wrote

thheller08:10:41

@urbanslug the (done) is called before the is executes

thheller08:10:22

the vector vs list doesn't matter

urbanslug08:10:23

thheller How do I make it block?

urbanslug08:10:46

The code I am seeing from other people is so cryptic

thheller08:10:29

uhm you can only call it once per test

urbanslug08:10:51

Some answer on SO says "Tests are executed synchronously, so if you go async the test-runner won't. In Clojure you need to block the test runner via <!!, in ClojureScript you have to return an async test object. "

thheller08:10:08

(deftest test1
  (async done
    (let [ch (chan)]
      (async/put! ch "Hello")
      (go (is (= "Hello" (<! ch)))
          (done)))))

thheller08:10:25

the deftest should do all the coordination and decide when to call done

urbanslug08:10:57

hmmm I don’t see what you did so differently from this

(defn adder
  []
  (go (+ 2 3)))

(deftest test-get-stats
  (async done
         (go (is (= (<! (adder)) 5)
                 (done)))))

urbanslug08:10:06

which fails thheller ☝️

urbanslug08:10:35

I messed up the done when you said it should be once per test

thheller08:10:38

(<! (adder))

thheller08:10:46

one less form

thheller08:10:07

ah wait nvm

thedavidmeister08:10:14

@iku000888 not sure how to get read-string working in the way you suggest in cljs

thheller08:10:37

why is adder a go?

thedavidmeister08:10:38

(read-string {:read-cond :allow} "(ns styles.card
  (:require [garden.units :as u]
            [styles.colours]
            [styles.spacing]
    #?(:clj [garden.def :refer [defstyles defrule]]))
  #?(:cljs (:require-macros [garden.def :refer [defstyles defrule]])))")

thedavidmeister08:10:51

(ns styles.card (:require [garden.units :as u] [styles.colours] [styles.spacing] [garden.def :refer [defstyles defrule]]))

thedavidmeister08:10:57

which seems fine

urbanslug08:10:07

Even your code fails for me

urbanslug08:10:31

@thheller

(ns milia.api.submissions-test
  (:require-macros [cljs.test :refer [is deftest async]]
                   [cljs.core.async.macros :refer [go]])
  (:require [milia.api.submissions :as sub]
            [cljs.test :as test]
            [cljs.core.async :refer [<! >! chan put! take!]]))



(deftest test1
  (async done
         (let [ch (chan)]
           (put! ch "Hello")
           (go (is (= "Hello" (<! ch)))
               (done)))))

urbanslug09:10:02

@thheller I just wanted a simple async function

urbanslug09:10:27

adder is async because it is simple

urbanslug09:10:47

Which doesn’t do anything really

urbanslug09:10:55

any go block will pass that test

thheller09:10:21

(deftest test1
  (let [ch (async/chan)]
    (async/put! ch "Hello")
    (async done
      (go (is (= "Hello" (<! ch)))
          (done))
      )))

thheller09:10:34

this works for me

iku00088809:10:18

Apparently you need to explicitly require cljs.reader to use the read-string function

thedavidmeister09:10:18

(cljs.reader/read-string "(ns styles.card
    (:require [garden.units :as u]
              [styles.colours]
              [styles.spacing]
      #?(:clj [garden.def :refer [defstyles defrule]]))
    #?(:cljs (:require-macros [garden.def :refer [defstyles defrule]])))”)

thheller09:10:42

@thedavidmeister cljs.reader does not support conditional reading

thedavidmeister09:10:45

Error: Could not find tag parser for ? in ...

iku00088809:10:33

Yeah... Just figured that out... @thheller

thedavidmeister09:10:39

but even so, as i’m running (prn (clojure.tools.namespace.find/find-namespaces-in-dir ( "src/cljc"))) in clj, does it matter?

thheller09:10:52

sorry I missed the context of what you are trying to do

thedavidmeister09:10:38

@thheller for some reason the #? stuff in my cljc ns is causing the ns to not appear when i run clojure.tools.namespace.find/find-namespaces-in-dir

thheller09:10:53

works for me?

thedavidmeister09:10:47

@thheller can you paste your code?

thheller09:10:05

its your code

thedavidmeister09:10:18

(ns styles.card
  (:require [garden.units :as u]
            [styles.colours]
            [styles.spacing]
    #?(:clj [garden.def :refer [defstyles defrule]]))
  #?(:cljs (:require-macros [garden.def :refer [defstyles defrule]])))

thheller09:10:32

you sure your file is in src/cljc not src/clj or src/cljs by accident? 😛

thedavidmeister09:10:35

i’m now wondering if the garden boot task is doing something...

thheller09:10:12

(clojure.tools.namespace.find/find-namespaces-in-dir ( "src/main"))
=> (styles.card)

martinklepsch09:10:08

@thedavidmeister: do you have an explicit dependency on clojure?

martinklepsch09:10:46

If not it might be possible that the pod the garden task uses ends up loading a different clojure version

thedavidmeister09:10:34

i added clojure in my dependencies, yeah

thedavidmeister09:10:45

nah i don’t think it’s the garden boot task

thedavidmeister09:10:00

(deftask garden
  "Wraps the garden task provided by boot-garden"
  [p pretty-print? bool "Pretty print the CSS output."]
  (comp
    (watch)
    (with-pass-thru [_]
      (require '[styles.compile])
      (styles.compile/foo))))

thedavidmeister09:10:15

(ns styles.compile
  (:require [garden.core]
            [clojure.tools.namespace.find]))

(defn foo []
  (prn (clojure.tools.namespace.find/find-namespaces-in-dir ( "src/cljc"))))

thedavidmeister09:10:27

(styles.animation styles.border styles.box-model styles.box-shadow styles.colours styles.compile styles.neat styles.spacing styles.typography)

thedavidmeister09:10:41

no styles.card 😞

thedavidmeister09:10:50

@martinklepsch on a side note, i don’t really understand the pods thing. What is it doing for the garden task?

thedavidmeister10:10:03

@thheller what version of clojure are you using?

thheller10:10:26

1.9.0-alpha13 but that shouldn't matter

thheller10:10:03

tried 1.8.0 same result

martinklepsch10:10:07

Have to go now but basically it helps isolate dependencies by providing a construct "pod" that can have a different classpath than the rest of the application

thedavidmeister10:10:42

i’m thinking i want my garden to sit alongside my application

thedavidmeister10:10:46

maybe that’s wrong

thheller10:10:09

have you tried in a REPL without boot?

thedavidmeister10:10:43

i tried with boot repl

thedavidmeister10:10:01

is there another way to get a repl that might find it?

thheller10:10:36

find src/cljc

thheller10:10:56

I somehow can't believe that the file is getting filtered 😛

thheller10:10:06

is it really there?

thheller10:10:48

and what happens if you (require 'styles.card)?

thedavidmeister10:10:56

oh i can require it just fine

thedavidmeister10:10:24

i checked that i can require it and prn the output of defstyles in both clj and cljs

thheller10:10:40

if I see that right (styles.animation styles.border styles.box-model styles.box-shadow styles.colours styles.compile styles.neat styles.spacing styles.typography)

thheller10:10:54

is missing styles.breakpoints as well?

thedavidmeister10:10:01

well that has the same issue

thheller10:10:30

strange indeed

thedavidmeister10:10:30

(ns styles.breakpoints
  (:require [garden.stylesheet :refer [at-media]]
            [garden.units :as u])
  #?(:cljs (:require-macros [styles.breakpoints :refer [defbreakpoint]])))

#?(:clj
    ; 
    (defmacro defbreakpoint [name media-params]
      `(defn ~name [& rules#]
         (at-media ~media-params
           [:& rules#]))))

thheller10:10:49

but they appear if you remove the #??

thedavidmeister10:10:53

any file i stick the cljc reader thingo into the ns for disappears from the list

thedavidmeister10:10:47

(ns styles.breakpoints
  (:require [garden.stylesheet :refer [at-media]]
            [garden.units :as u]))
  ; #?(:cljs (:require-macros [styles.breakpoints :refer [defbreakpoint]])))

#?(:clj
    ; 
    (defmacro defbreakpoint [name media-params]
      `(defn ~name [& rules#]
         (at-media ~media-params
           [:& rules#]))))

thedavidmeister10:10:57

boot.user=> (prn (clojure.tools.namespace.find/find-namespaces-in-dir ( "src/cljc")))
(styles.animation styles.border styles.box-model styles.box-shadow styles.breakpoints styles.colours styles.compile styles.neat styles.spacing styles.typography)

thedavidmeister10:10:29

i can use #? in the file

thheller10:10:05

I know nothing about boot but did you check if the classpath is clean?

thheller10:10:15

no conflicting versions of tools.reader on there?

thedavidmeister10:10:49

not sure how to do that

thedavidmeister10:10:12

i can set a dependency though

thedavidmeister10:10:35

what version are you using?

thedavidmeister10:10:27

actually it does look like ring depends on [org.clojure/tools.namespace "0.2.10”]

thheller10:10:58

org.clojure/tools.reader "1.0.0-beta1"

thheller10:10:06

via clojurescript

thheller10:10:37

+ org.clojure/tools.namespace "0.2.11"

thedavidmeister10:10:56

[org.clojure/tools.reader "1.0.0-beta3”]

thedavidmeister10:10:19

boot.user=> (prn (clojure.tools.namespace.find/find-namespaces-in-dir ( "src/cljc")))
(styles.animation styles.border styles.box-model styles.box-shadow styles.breakpoints styles.card styles.colours styles.compile styles.neat styles.spacing styles.typography)

thedavidmeister10:10:02

hopefully it’s an upgrade in tools.namespace and not a regression in tools.reader...

thedavidmeister10:10:54

so i think it was just a bug in 0.2.10

thheller10:10:53

hehe good, i was out of ideas 😉

thedavidmeister10:10:14

in fairness, this is probably the first thing i should have tried

thedavidmeister10:10:19

i just assumed i was doing something wrong in cljc

thheller10:10:25

I would expect some kind of error in that case

thedavidmeister10:10:41

this is the first time i’ve used cljc

thedavidmeister10:10:03

all this so i can pass my data from garden to js libs 😛

timmywil14:10:50

Hi! I am new to clojurescript (and clojure). I'm trying to work out an architecture for an isomorphic web app (clojure back-end). I know there are some examples of server-rendering in reagent. Examples aren't so good for rum because it's unopinionated. That's one issue. The other is how to handle CSS. There are some suggestions out there to use sass, but that's not enough. What about autoprefixer, removing unused CSS, etc.? It seems like there aren't any lein or boot tools for this yet. Is my only option to use node for post-processing CSS?

borkdude14:10:56

I don’t understand the output of (type #js [1 2 3]):

#object[Array "function Array() { [native code] }”]

timmywil14:10:52

I'm new to CLJS, so I'm not sure what the hash means, but "Array" is the right constructor. The 2nd part looks like a string representation of the Array constructor in JS. "[native code]" is binary code.

thheller14:10:21

@borkdude type in cljs gives you the constructor of the object. in your case that is the Array function which is native code

bostonaholic14:10:05

cljs.user=> (source type)
(defn type
  "Return x's constructor."
  [x]
  (when-not (nil? x)
    (.-constructor x)))

anmonteiro14:10:11

@borkdude you might be looking for goog/typeOf

cljs.user=> (goog/typeOf #js [])
"array"

thheller14:10:18

the #object... is just the printer trying to print edn compatible output

dnolen17:10:15

Unsurprisingly Closure dominates load time and run time against all other JS tooling as project complexity increases

keeds17:10:40

Is ^^^ a bug introduced with CLJS-1658?

dnolen18:10:30

@keeds probably file a bug - you should be able to reproduce this on js/String though, I would leave anything that involves a browser out of the issue

dnolen18:10:09

@keeds actually I cannot replicate on js/String so will need more information

keeds18:10:48

Ok. Will do.

lwhorton19:10:08

When dealing with cljs/js interop and libraries that use this-context method chaining, what’s the best way to invoke that from cljs? for example:

someLibrary(a_div).doSomething({ args: true}).doAnotherThing()
Internally someLibrary utilizes a lot of if (this.foo) where the initial object constructed by someLibrary(a_div) binds the this.

lwhorton19:10:47

If I try a thread macro the this context always comes up null/undefined, which I assume is due to the rewriting of the macro not knowing/caring about this?

lwhorton19:10:15

My other suspicion is that perhaps I'm misusing closure’s object.get, expecting it to do something it’s not…

(-> (js/someLib node)
        (#((oget % "foo") (clj->js {:some :args})))
        (#((oget % "on") "resize" (clj->js {:some :more-args}))))

dnolen19:10:08

@lwhorton just use method chaining

dnolen19:10:24

(.. js/foo (bar a b) (baz c d) (woz e f))

dnolen19:10:44

(.foo x) is just sugar for (. x (foo))

dnolen19:10:22

(.. x …) is just repeated method calls using each preceding return value as the first argument

anmonteiro19:10:55

@dnolen FWIW I can repro with js/String in 1.9.293:

cljs.user=> (extend-type js/String ISeqable (-seq [x] x))
            ⬆
WARNING: Extending an existing JavaScript type - use a different symbol name instead of js/String e.g string at line 1
#object[Function "function (x){
var x__$1 = this;
return x__$1;
}"]
cljs.user=> (first "asd")
No protocol method ISeq.-first defined for type object: asd

dnolen19:10:38

@anmonteiro try it with a custom protocol

dnolen19:10:42

that’s what I tried

lwhorton19:10:56

aha... sugar gave me semi-colon cancer? I’ll have to investigate how .. handles advanced compilation to avoid munging issues, but thanks for the tip @dnolen ~

dnolen19:10:18

@lwhorton there’s no implications for advanced compilation

dnolen19:10:48

the only time you should ever be thinking about advanced compilation is when you’re including some random JS library

dnolen19:10:08

and the only thing you ever should be doing is providing externs for that stuff

lwhorton19:10:35

I was hoping to use object.get and object.set and strings to avoid having to write an externs entirely

dnolen19:10:46

I don’t know why

anmonteiro19:10:51

a custom protocol works.. still don't understand why the above wouldn't?

lwhorton19:10:00

since I’m only using a tiny piece of the library, and a complete externs is a much larger committment

dnolen19:10:05

@anmonteiro well there’s our bug 🙂

dnolen19:10:11

@lwhorton you never need complete externs

dnolen19:10:21

I write one line externs files all the time

anmonteiro19:10:37

FWIW it doesn't work on 1.9.229, so I could also be doing something wrong

dnolen19:10:40

this externs fear needs to stop 🙂

lwhorton19:10:55

oh… I thought I was being a bad cljsjs citizen by not writing a full externs

dnolen19:10:15

@lwhorton if you’re writing an app and you’re not publishing something who cares how complete your externs are

dnolen19:10:11

all this time trying to avoid writing externs is just a huge waste of time - IMHO

dnolen19:10:48

eventually we will automate what we can - but it’s really not that hard to write a couple of line for whatever you are using from some random library if that’s all you need

dnolen19:10:18

@anmonteiro the issue as @keeds reported it is really a red herring anyway

dnolen19:10:36

@anmonteiro if this is failing it’s most certainly going wrong at satisfies? or implements?

anmonteiro19:10:56

should (extend-type js/String ISeqable (-seq [x] x)) work though?

anmonteiro19:10:11

I think it should, but there could be something preventing that

dnolen19:10:24

but at least we know some things are working and others aren’t - which is a bit more information

lwhorton19:10:07

once again, much appreciated @dnolen

dnolen19:10:50

this is what I did for transit-js, value and done weren’t in the Google Closure externs yet for the JS Iterator stuff

dnolen19:10:15

not exactly a lot of work

lwhorton19:10:44

I’ve never seen an externs operate directly on Object.. always some variable = function () {}; variable.prototype.foo = ...

lwhorton19:10:23

that must mean you can use a callback fn that’s handed a transit object and (.-tag/rep/done/etc. obj) becomes un-mangled (or rather never mangeld)?

dnolen19:10:28

@lwhorton all an externs file is a bunch of top level names

dnolen19:10:05

if you put a property on Object, then when Closure sees a property with that name and infers type Object, it knows not to rename

lwhorton19:10:38

I see.. that’s much simpler. (— stupid redacted—)

darwin19:10:14

A counter point could be that when you set some arbitrary wire-related externs on Object it can bite you a few months later when you forget about it and start wondering why some arbitrary names do not get renamed in advanced mode in cases where you would expect them to. It can be pretty hard to track down if such externs are bundled in some library or otherwise hidden from plain sight. There is no tool which would tell you “this happened because of Y”, at least I’m not aware of any.

lwhorton19:10:12

the real solution is everyone should stop using javascript and write everything in cljs from here forward, obviously

dnolen19:10:14

@darwin “wire” related externs doesn’t make sense

dnolen19:10:23

you don’t need externs for wire stuff

dnolen19:10:30

that’s what goog.object is for

dnolen19:10:15

the more people keep conflating the reasons for this stuff the longer everyone stays collectively in the dark

darwin19:10:34

but you have to know it, you have to know, that the object you are passed is “external data” so you should use string names to access its properties, the line can be pretty blurry here

dnolen19:10:32

there are static properties of your program - this is what externs are for

dnolen19:10:47

then there is runtime data - you don’t need externs

darwin19:10:01

yes, I agree

dnolen19:10:17

the more you bang this drum

dnolen19:10:23

the faster newcomers will figure it out

darwin19:10:45

more precisely "you don’t need externs and you must NOT use dot property names"

dnolen19:10:15

right don’t use dot property names for dealing with data

dnolen19:10:41

speaking about it this way also has wider implications even when we get externs inference

darwin19:10:48

but still, I’m afraid, this sounds all clear and easy to you and me, but newcomers really struggle to distinguish these nuances without deeper understanding how closure compiler, javascript and clojurescript work

dnolen19:10:51

we can probably infer static properties of programs

dnolen19:10:01

we will probably not be able to infer dynamic properties of data

dnolen19:10:27

@darwin sounds like a guide worth writing for http://clojurescript.org for someone who is willing 🙂

lwhorton19:10:47

your word choice makes me think this is work under way with the compiler? that will be exciting

dnolen19:10:14

@lwhorton there’s some prototype work I got started on and it will probably happen relatively soon

dnolen19:10:30

however I’m side tracked a bit on Node.js / ES6 integration work

timgilbert20:10:06

Hello everybody, anyone know of a decent logging library that works for both clojure.tools.logging in JVM Clojure and (.log js/console) in browser-based ClojureScript?

danielcompton20:10:57

timbre has adapters for c.t.l

danielcompton20:10:04

and works in browser

dnolen20:10:37

@timgilbert Google Closure also has one if shared logging isn’t a goal

timgilbert20:10:58

Oh, interesting, will look at that

timgilbert20:10:43

Not sure if timbre will work or not, but I'll give it a look too

darwin20:10:30

@timgilbert I started working on one, but my goal is not to make it 100% compatible, I will just mimic clojure.tools.logging api in cljs and make it cljs-devtools friendly, e.g. it will use pprint in advanced mode (if not elided)

darwin20:10:46

@timgilbert if you have ideas to share, I would like to hear them - I’m still not 100% sure how I want to approach it, want something like https://github.com/adzerk-oss/cljs-console, but without string printing, and api similar to clojure.tools.logging, in dev mode it would just boil down to console.log calls, in advanced mode some or all log levels can be elided and those which are not would be presented with pprint or similar alternative code-path

darwin20:10:35

also I want to support formatting from devtools, but in a different way, not inheriting inflexible C-format strings which don’t compose

timgilbert20:10:59

That all sounds great @darwin. Right now I'm using shodan and its killer feature is that it uses macros, so that the log statements in the console all point back to the line in the actual source file where the log statement was, instead of all pointing to some line of code in a library somewhere

darwin20:10:47

@timgilbert yes, I will support that as well

timgilbert20:10:36

Personally my use-case is that I have a fairly small set of cross-platform code that I need to call from both Clojure and ClojureScript, and I'd like to be able to minimize the amount of #?(:clj :cljs) blocks in there, to zero if possible

timgilbert20:10:56

Right now I'm looking at just writing a thin macro layer, which isn't hard to do but I thought I'd check if something already existed first. I did look at timbre briefly but some other folks on my team seem to dislike it