Hi, I’m Javan. I make resilient web applications.

You can reach me by email, find me on Mastodon and on Twitter, and explore my open-source work on GitHub.

I work at Steady as VP of engineering, currently.

Prior to Steady, I spent at decade at Basecamp37signals where I co-authored several open-source JavaScript libraries including Hotwire (Stimulus + Turbo) and Trix, and helped build new Ruby on Rails frameworks like Action Cable and Action Text.

I’m a former member of the Rails Committer team, an active contributor, and a seasoned veteran with 15 years of hands-on experience building robust, high-traffic, thoughtful applications.


60 things I’ve done on the internet since 2013.

  1. mastodon.social/@javan/109869913242555450
  2. twitter.com/javan/status/1516057444463616000
  3. github.com/rails/website/pull/28
  4. twitter.com/javan/status/1486059026064584711
  5. twitter.com/javan/status/1466119118420164612
  6. twitter.com/javan/status/1463593950183403524
    Javan Makhmali Javan Makhmali @javan

    Update: I’m no longer looking for a new role. More soon, but for now a huge thank you to everyone who reached out and helped spread the word. I’m humbled and grateful.💞

    1 2
  7. twitter.com/javan/status/1388215254685990915
  8. bugs.webkit.org/show_bug.cgi?id=222657

    Bug 222657 — Media element never populates its UA shadow if it was initially created in a document without browsing context

    When input and media elements are adopted with document.adoptNode() from a foreign document (via DOMParser, for example) they lose their native shadow DOM roots and are left in a mostly broken state: <input>s lose their native UI, and <video> and <audio> elements lose their native controls.
    To reproduce, open the attached adopt-node-bug.html file in Safari and compare the correct middle column (imported node) to the incorrect right column (adopted node).
    Tested in Safari v14.0.3 (16610. on macOS v11.2.2, and mobile Safari on iOS v14.4.
  9. twitter.com/javan/status/1357800714018443270
    Javan Makhmali Javan Makhmali @javan

    This shit is totally unacceptable, @canary. I’ve wasted enough time completing your dark pattern maze. Cancel my account.

    648 132
  10. youtube.com/watch?v=hdiLqJc0ySc
  11. bugs.webkit.org/show_bug.cgi?id=211620
  12. bugs.chromium.org/p/chromium/issues/detail?id=1043564#c7

    Re: Bug 1043564 — Incorrect range calculated during a 'deleteSoftLineBackward' event

    Here is another example that demonstrates the issue. It works by canceling the "beforeinput" event and applying the target range to the selection to illustrate it.
    1. Open the attached "select-target-range.html" file in Chrome
    2. Place the cursor at the end of each line and press Cmd+Delete
    3. Observe the selection, which represents the target range
    4. Not that it doesn't match the actual range of text to be removed
  13. bugs.webkit.org/show_bug.cgi?id=203116

    Bug 203116 — Native text substitutions interfere with HTML <datalist> options resulting in crash

    To reproduce:
    1. Open System Preferences → Keyboard → Text and add entry to replace "cat" with "Cat"
    2. Open the attached datalist.html file in Safari
    3. Ensure that the menu option for Edit → Substitutions → Text Replacement is selected
    3. Type "cat" in the text field and wait for the "Cat" replacement overlay to appear
    4. Click any option from the expanded <datalist> menu and Safari will crash
    Tested on macOS 10.14.6 (18G103) with Safari 13.0.2 (14608. and Safari TP Release 94 (Safari 13.1, WebKit 14609.1.6.1). I believe this issue occurs in Safari 12.1 as well.
  14. weblog.rubyonrails.org/2019/8/15/Rails-6-0-final-release/

    Rails 6.0: Action Mailbox, Action Text, Multiple DBs, Parallel Testing, Webpacker by default, and Zeitwerk

    Dealing with incoming email, composing rich-text content, connecting to multiple databases, parallelizing test runs, integrating JavaScript with love, and rewriting the code loader. These are fundamental improvements to the fundamentals of working with the web and building fast and fresh applications. This is the kind of work we’ve been doing for the past fifteen years, and we’re still at it. THIS IS RAILS SIX!

  15. bugs.chromium.org/p/chromium/issues/detail?id=992862

    Bug 992862 — Cutting from a contentEditable element causes slow forced reflow

    Steps to reproduce the problem:
    1. Open the attached cut.html file and click "Cut All" (or, use the Edit → Cut menu)
    What is the expected behavior?
    The cut operation completes in a reasonable amount of time.
    What went wrong?
    The cut operation takes several seconds to complete. The Performance inspector reveals a very slow Layout task. See the attached .gif for a comparison by browser.
    Chrome: 5,177ms
    Firefox: 19ms
    Safari: 147ms
    Did this work before? N/A 
    Does this work in other browsers? N/A
    Chrome version: 76.0.3809.100  Channel: stable
    OS Version: OS X 10.14.6
    Flash Version:
  16. topenddevs.com/podcasts/javascript-jabber/episodes/jsj-376-trix-a-rich-text-editor-for-everyday-writing-with-javan-makhmali

    Trix: A Rich Text Editor for Everyday Writing with Javan Makhmali

    Today’s guest is Javan Makhmali, who works for Basecamp and helped develop Trix. Trix is a rich text editor for the web, made purposefully simple for everyday use instead of a full layout tool. Trix is not the same as Tiny MCE, and Javan discusses some of the differences. He talks about the benefits of using Trix over other native browser features for text editing. He talks about how Trix has simplified the work at Basecamp, especially when it came to crossing platforms. Javan talks more about how Trix differs from other text editors like Google Docs and contenteditable, how to tell if Trix is functioning correctly, and how it works with Markdown.

  17. twitter.com/javan/status/1151914387428564992
  18. github.com/github/eventlistener-polyfill/pull/13
  19. twitter.com/javan/status/1111329710842101760
  20. remoteruby.com/2260490/13761112
  21. github.com/electron/electron/issues/16418

    Top of window with "hiddenInset" title bar becomes unresponsive after exiting fullscreen electron/electron#16418

    • Output of node_modules/.bin/electron --version: v4.0.1
    • Operating System (Platform and Version): macOS 10.14.2

    Expected Behavior
    The top of the window should continue responding to clicks after exiting from fullscreen.

    Actual behavior
    The top ~40px stops responding to clicks after exiting from fullscreen.

    To Reproduce

    1. Clone and run https://github.com/javan/electron-quick-start/tree/hidden-inset-mojave
    2. Click the black bar at the top of the window. It should turn purple while the mouse is down.
    3. Enter and exit fullscreen.
    4. Click the top of the window and note that it no longer turns purple.
    $ git clone https://github.com/javan/electron-quick-start.git -b hidden-inset-mojave
    $ npm install
    $ npm start


    Additional Information
    This issue is quite similar to #14529.

  22. github.com/basecamp/trix/pull/580

    Input Events Level {1,2} basecamp/trix#580

    This PR improves the way Trix records input—particularly on mobile—by adding support for Level 2 InputEvents, which are currently implemented in Safari and Chrome, and in their companion webview implementations on iOS and Android. In all other browsers, Trix will continue to work exactly as it does today. Here's how:

    First, Trix tests for InputEvent support:

    supportsInputEvents: do ->
    typeof InputEvent isnt "undefined" and
    "inputType" of InputEvent.prototype and
    "data" of InputEvent.prototype

    Then, when a <trix-editor> initializes, its editor controller installs one of two input controllers:

    @inputController = new Trix["Level#{Trix.config.input.getLevel()}InputController"](@editorElement)
    @inputController.delegate = this
    @inputController.responder = @composition

    1. Trix.Level2InputController if input events are supported
    2. Trix.Level0InputController, which is equivalent to the current Trix.InputController, if input events are not supported

    Fixes #575
    Fixes #574
    Fixes #520
    Fixes #494
    Fixes #396
    Fixes #315
    Fixes #178
    Closes #564

    2 4 4 1
  23. therubyonrailspodcast.com/247
  24. twitter.com/javan/status/1057247269307727872
  25. twitter.com/javan/status/1044958902369103874
  26. github.com/electron/electron/issues/14529

    macOS Mojave: Unclickable region in windows with "hiddenInset" title bar electron/electron#14529

    Expected Behavior
    Clicking any region of the window should activate the element under the cursor.

    Actual behavior
    A small region in the top-center of the window isn't clickable.

    To Reproduce

    $ git clone https://github.com/javan/electron-quick-start.git -b hidden-inset-mojave
    $ npm install
    $ npm start

    macOS Mojave 10.14 Beta (18A384a) 👎

    macOS High Sierra 10.13.6 👍

    Additional Information
    This issue impacts any application with critical navigation elements on the top of the page. For us (Basecamp 3), the "Hey!" menu is very difficult to click:
    screen shot 2018-09-10 at 1 36 50 pm

    <notch-joke> </notch-joke>

    It's likely that Mojave will be released this week during the Apple Special Event on September 12.

  27. twitter.com/javan/status/1027642135712018437
    Javan Makhmali Javan Makhmali @javan

    Kudos to @getsentry for revealing the culprit of a JavaScript error that would’ve been a serious head scratcher. jQuery was being injected by some browser extension(s), clobbering our own version and nuking the jQuery plugins we rely on. 💥

    17 4
  28. github.com/basecamp/trix/pull/523

    Image galleries basecamp/trix#523

    This pull request adds support for displaying groups of images in a grid, AKA image galleries. It works by automatically grouping adjacent image attachments into blocks, and styling the block's attachment elements to form a grid. The layout is 100% CSS-based so it can easily be customized or adjusted for different viewport sizes using media queries. Out of the box, Trix supports two and three column grids controlled by just a few styles in trix.css:

    .attachment-gallery {
    display: flex;
    flex-wrap: wrap;
    position: relative;
    margin: 0;
    padding: 0;
    .attachment {
    flex: 1 0 33%;
    padding: 0 0.5em;
    max-width: 33%;
    &.attachment-gallery--4 {
    .attachment {
    flex-basis: 50%;
    max-width: 50%;

    Additional changes

    1. Attachment toolbars now include file metadata, and their markup and has been revised to align more closely with <trix-toolbar>'s.
      screen shot 2018-07-25 at 9 43 48 am

    2. Text formatting can no longer be applied to attachments because, in part, the resulting HTML would break image gallery layouts. It also had weird/inconsistent results before and could easily happen accidentally (dropping images into bold text, for example).

    3. Attachments with href attributes now render <a>s inside their <figure>s.

      <!-- BEFORE -->
      <a href="">
          <img src="">
      <!-- AFTER -->
        <a href="">
          <img src="">
    10 11
  29. github.com/basecamp/trix/pull/516

    Custom Elements v1 (and v0) basecamp/trix#516

    This pull requests adds support for Custom Elements v1, the official standardized spec recognized by all major platforms. v1 has already landed in Chrome, Safari, and Opera, and development is underway in Firefox and Microsoft Edge.

    Custom Elements v1 and v0 are conceptually identical, although they don't share much in common API-wise. For better or worse, those API differences make v1 more difficult to adopt (unless you're only targeting modern browsers and delivering ES2015+ JavaScript code).

    v1 gotchas…

    Caveat: an unpolyfillable upgrade
    There are things that current V1 makes impossible to reproduce via plain JS and the most obvious is the JS instance upgrade. In older engines, extending any DOM native class is not enough to have a proper DOM instance.


    ES5 vs ES2015
    The custom elements v1 spec is not compatible with ES5 style classes. This means ES2015 code compiled to ES5 will not work with a native implementation of Custom Elements.[0] While it's possible to force the custom elements polyfill to be used to workaround this issue (by setting (customElements.forcePolyfill = true; before loading the polyfill), you will not be using the UA's native implementation in that case.

    Since this is not ideal, we've provided an alternative: native-shim.js. Loading this shim minimally augments the native implementation to be compatible with ES5 code. We are also working on some future refinements to this approach that will improve the implementation and automatically detect if it's needed.

    [0] The spec requires that an element call the HTMLElement constructor. Typically an ES5 style class would do something like HTMLElement.call(this) to emulate super(). However, HTMLElement must be called as a constructor and not as a plain function, i.e. with Reflect.construct(HTMLElement, [], MyCEConstructor), or it will throw.


    In my testing, none of the v1 polyfills worked reliably in older versions of Safari or on older Android devices. They're also far more invasive, attempting to patch every possible means of content creation / insertion. Miss one (like Element#insertAdjacentHTML) and custom elements slip through without upgrading.

    v0 on the other hand, even though it's only officially supported in Chrome, has far better support thanks to several battle hardened, MutationObserver-based polyfills. And so, this pull requests preserves support for v0 too.

    To smooth over the v1/v0 API differences, this change introduces its own distinct naming convention for lifecycle callbacks: initialize(), connect(), disconnect() (thanks, Stimulus!), and automatically maps them to the supported custom element API.


    initialize() → connectedCallback() (once)
    connect()    → connectedCallback()
    disconnect() → disconnectedCallback()

    v0 (polyfilled if necessary):

    initialize() → createdCallback() 
    connect()    → attachedCallback()
    disconnect() → detachedCallback()

    Fixes #495

  30. twitter.com/javan/status/1012351745043902464
  31. Show me 30 more…

The end! 👋