Full Code of PrecursorApp/om-i for AI

master 30c1f248a046 cached
10 files
23.2 KB
6.4k tokens
1 requests
Download .txt
Repository: PrecursorApp/om-i
Branch: master
Commit: 30c1f248a046
Files: 10
Total size: 23.2 KB

Directory structure:
gitextract_8uedg_ze/

├── .gitignore
├── CHANGES.md
├── README.md
├── circle.yml
├── project.clj
├── resources/
│   ├── om-i.css
│   └── om-i.less
└── src/
    └── om_i/
        ├── core.cljs
        ├── hacks.cljs
        └── keyboard.cljs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
pom.xml
*jar
/lib/
/classes/
/out/
/target/
.lein-deps-sum
.lein-repl-history
.lein-plugins/
/script/


================================================
FILE: CHANGES.md
================================================
## 0.1.8
### Changes
* Stop depending on _rootNodeId so that we can support React 0.13.

## 0.1.7
### Changes
* Fix ordering of render-ms and mount-ms

## 0.1.6
### Changes
* fix borked release to Clojars of 0.1.5

## 0.1.5
### Changes
* Note: this released was messed up somehow, use 0.1.6 instead
* fix un-required goog.string.format. Thanks [@cldwalker](https://github.com/cldwalker)!

## 0.1.4
### Changes
* fixes compilation error from requiring `goog`

## 0.1.3
### Changes
* remove unused cljs-time requires


================================================
FILE: README.md
================================================
# Om-instrumentation

Instrumentation helper for Om applications.

[![Circle CI](https://circleci.com/gh/PrecursorApp/om-i.svg?style=svg)](https://circleci.com/gh/PrecursorApp/om-i)

## Overview

Om-i (pronounced "Oh, my!") helps you identify the components in your [Om](https://github.com/omcljs/om) application that are being passed too much of your app state and rendering unnecessarily. It provides useful statistics about render times and frequencies for all of your components.

You can see it live on [Precursor](https://precursorapp.com), a collaborative drawing application. Use Ctrl+Alt+Shift+j to toggle it.

<a href="https://precursorapp.com">
 <img src="http://dtwdl3ecuoduc.cloudfront.net/om-i/instrumentation.gif" />
</a>

## Setup

### Dependencies
Add Om-i to your project's dependencies

```
[precursor/om-i "0.1.8"]
```

### Enable the instrumentation

Use Om-i's custom descriptor so that it can gather render times for your components. To enable it globally, use the `:instrument` opt in `om/root`

```clojure
(om/root
  app-component
  app-state
  {:target container
   :instrument (fn [f cursor m]
                 (om/build* f cursor
                            (assoc m
                                   :descriptor om-i.core/instrumentation-methods)))})
```

### Mount the component

Add the following somewhere in your setup code. If you're using figwheel, place it somewhere that won't get reloaded.

```clojure
(om-i.core/setup-component-stats!)
```

Om-i renders its statistics in a separate root so that it doesn't interact with your application.

It will create a `div` in the body with classname "om-instrumentation" by default and assign three keyboard shortcuts: Ctrl+Alt+Shift+j to bring down the statistics menu, Ctrl+Alt+Shift+k to clear the statistics, and Ctrl+Alt+Shift+s to switch the sort order.


You can override the defaults with:

```clojure
(om-i.core/setup-component-stats! {:class           "om-instrumentation"
                                   :clear-shortcut  #{"ctrl" "alt" "shift" "k"}
                                   :toggle-shortcut #{"ctrl" "alt" "shift" "j"}
                                   :sort-shorcut    #{"ctrl" "alt" "shift" "s"}})
```

### Styles

You need to set up css styles to handle displaying the instrumentation when it's opened. There are sample less and css files in the resources directory.

If you want to try out Om-i, or just use it in development, we've provided a helper that will embed a style tag with the syles from resources/om-i.min.css.

```clojure
(om-i.hacks/insert-styles)
```

It's not recommended to use this in production.

### Wrapping a pre-existing descriptor

If you're already using a custom descriptor, you can still use Om-i. Here's an example wrapping Om's `no-local-descriptor`.

```clojure
(let [methods (om-i.core/instrument-methods om/no-local-state-methods)
      descriptor (om/no-local-descriptor methods)]
  (om/root
    app-component
    app-state
    {:target container
     :instrument (fn [f cursor m]
                   (om/build* f cursor (assoc m :descriptor descriptor)))}))
```

## Acknowledgements

Thanks to [@sgrove](https://github.com/sgrove) for his keyboard handling code. Om-i uses a minimal version of the code he wrote for Precursor. There is an older, [public version of the code in Omchaya](https://github.com/sgrove/omchaya/blob/master/src/omchaya/components/key_queue.cljs).

Thanks to [@brandonbloom](https://github.com/brandonbloom) for demonstrating how to use descriptors in Om. [Related blog post](http://blog.circleci.com/local-state-global-concerns/).

Thanks to [@swannodette](https://github.com/swannodette) for releasing Om.


## License

Copyright © 2015 PrecursorApp

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.


================================================
FILE: circle.yml
================================================
dependencies:
  post:
    - lein cljsbuild once

test:
  override:
    - echo no tests


================================================
FILE: project.clj
================================================
(defproject precursor/om-i "0.1.8"
  :description "Instrumentation helpers for Om applications"
  :url "https://github.com/PrecursorApp/om-i"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-2755" :scope "provided"]
                 [org.omcljs/om "0.8.8" :scope "provided"]]

  :plugins [[lein-cljsbuild "1.0.4"]]
  :cljsbuild {:builds [{:id "test"
                        :source-paths ["src"]
                        :compiler {:output-to "script/tests.simple.js"
                                   :output-dir "script/out"
                                   :source-map "script/tests.simple.js.map"
                                   :output-wrapper false
                                   :optimizations :simple}}]}

  :source-paths ["src"])


================================================
FILE: resources/om-i.css
================================================
@keyframes in-fade-top-soft {
  0% {
    opacity: 0;
    transform: translate3d(0, -4rem, 0);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}
@-webkit-keyframes in-fade-top-soft {
  0% {
    opacity: 0;
    -webkit-transform: translate3d(0, -4rem, 0);
  }
  100% {
    opacity: 1;
    -webkit-transform: none;
  }
}
.om-instrumentation {
  user-select: none;
  -moz-user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
  color: #888888;
  position: fixed;
  z-index: 1000;
  top: 0;
  left: 0;
  width: 100%;
}
.instrumentation-table {
  -webkit-animation: in-fade-top-soft 500ms;
  animation: in-fade-top-soft 500ms;
  background-color: rgba(0, 0, 0, 0.6);
  font-family: Monaco, monospace;
  font-size: .75rem;
  line-height: 2;
  width: 100%;
}
.instrumentation-table th {
  color: #ffffff;
  line-height: 1rem;
  padding: 1.5rem 0;
  text-transform: uppercase;
  text-align: left;
}
.instrumentation-table th:not(:first-child) {
  text-align: center;
}
.instrumentation-table th.left {
  text-align: left;
}
.instrumentation-table th.right {
  text-align: right;
}
.instrumentation-table th,
.instrumentation-table td {
  white-space: pre-wrap;
}
.instrumentation-table th:first-child,
.instrumentation-table td:first-child {
  padding-left: 1.5rem;
}
.instrumentation-table tbody tr:nth-child(odd) {
  background-color: rgba(0, 0, 0, 0.4);
}
.instrumentation-table tbody td:nth-child(even) {
  text-align: right;
  border-right: 1px dashed;
  padding-right: .5em;
}
.instrumentation-table tbody td:nth-child(odd) {
  text-align: left;
  padding-left: .5em;
}
.instrumentation-table tbody td:first-child {
  padding-left: 1.5rem;
}
.instrumentation-table tfoot td {
  line-height: 1rem;
  text-align: center;
  padding: 1.5rem 0;
}
.instrumentation-table small {
  font-size: 1em;
  opacity: .5;
}


================================================
FILE: resources/om-i.less
================================================
@black: #000;
@white: #fff;
@gray:  #888;

@font_mono: Monaco, monospace;
@zindex-instrumentation: 1000;

@tile: (1rem * 4);
@menu_padding: 1.5rem;

@keyframes in-fade-top-soft {
  0%   { opacity: 0; transform: translate3d(0, -@tile, 0);}
  100% { opacity: 1; transform: none;}
}
@-webkit-keyframes in-fade-top-soft {
  0%   { opacity: 0; -webkit-transform: translate3d(0, -@tile, 0);}
  100% { opacity: 1; -webkit-transform: none;}
}
.om-instrumentation {
  user-select: none;
  -moz-user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
  color: @gray;
  position: fixed;
  z-index: @zindex-instrumentation;
  top: 0;
  left: 0;
  width: 100%;
}
.instrumentation-table {
  -webkit-animation: in-fade-top-soft 500ms;
  animation: in-fade-top-soft 500ms;
  background-color: fade(@black, 60);
  font-family: @font_mono;
  font-size: .75rem;
  line-height: 2;
  width: 100%;
  th {
    color: @white;
    line-height: 1rem;
    padding: @menu_padding 0;
    text-transform: uppercase;
    text-align: left;
    &:not(:first-child) {
      text-align: center;
    }
    &.left {
      text-align: left;
    }
    &.right {
      text-align: right;
    }
  }
  th,
  td {
    white-space: pre-wrap;
    &:first-child {
      padding-left: @menu_padding;
    }
  }
  tbody {
    tr {
      &:nth-child(odd) {
        background-color: fade(@black, 40);
      }
    }
    td {
      &:nth-child(even) {
        text-align: right;
        border-right: 1px dashed;
        padding-right: .5em;
      }
      &:nth-child(odd) {
        text-align: left;
        padding-left: .5em;
      }
      &:first-child {
        padding-left: @menu_padding;
      }
    }
  }
  tfoot {
    td {
      line-height: 1rem;
      text-align: center;
      padding: @menu_padding 0;
    }
  }
  small {
    font-size: 1em;
    opacity: .5;
  }
}


================================================
FILE: src/om_i/core.cljs
================================================
(ns om-i.core
  (:require [clojure.string :as str]
            [goog.dom]
            [goog.string :as gstring]
            [goog.string.format]
            [om.core :as om :include-macros true]
            [om.dom :as dom :include-macros true]
            [om-i.keyboard :as keyboard]))

;; map of display name to component render stats, e.g.
;; {"App" {:last-will-update <time 3pm> :display-name "App" :last-did-update <time 3pm> :render-ms [10 39 20 40]}}
(defonce component-stats (atom {}))

(defn wrap-will-update
  "Tracks last call time of componentWillUpdate for each component, then calls
   the original componentWillUpdate."
  [f]
  (fn [next-props next-state]
    (this-as this
      (swap! component-stats update-in [((aget this "getDisplayName"))]
             merge {:display-name ((aget this "getDisplayName"))
                    :last-will-update (goog/now)})
      (.call f this next-props next-state))))

(defn wrap-did-update
  "Tracks last call time of componentDidUpdate for each component and updates
   the render times (using start time provided by wrap-will-update), then
   calls the original componentDidUpdate."
  [f]
  (fn [prev-props prev-state]
    (this-as this
      (swap! component-stats update-in [((aget this "getDisplayName"))]
             (fn [stats]
               (let [now (goog/now)]
                 (-> stats
                   (assoc :last-did-update now)
                   (update-in [:render-ms] (fnil conj [])
                              (max (- now (:last-will-update stats now)) 0))))))
      (.call f this prev-props prev-state))))

(defn wrap-will-mount
  "Tracks last call time of componentWillMount for each component, then calls
   the original componentWillMount."
  [f]
  (fn []
    (this-as this
      (swap! component-stats update-in [((aget this "getDisplayName"))]
             merge {:display-name ((aget this "getDisplayName"))
                    :last-will-mount (goog/now)})
      (.call f this))))

(defn wrap-did-mount
  "Tracks last call time of componentDidMount for each component and updates
   the render times (using start time provided by wrap-will-mount), then
   calls the original componentDidMount."
  [f]
  (fn []
    (this-as this
      (swap! component-stats update-in [((aget this "getDisplayName"))]
             (fn [stats]
               (let [now (goog/now)]
                 (-> stats
                   (assoc :last-did-mount now)
                   (update-in [:mount-ms] (fnil conj [])
                              (max (- now (:last-will-mount stats now)) 0))))))
      (.call f this))))

(defn instrument-methods [methods]
  (-> methods
    (update-in [:componentWillUpdate] wrap-will-update)
    (update-in [:componentDidUpdate] wrap-did-update)
    (update-in [:componentWillMount] wrap-will-mount)
    (update-in [:componentDidMount] wrap-did-mount)))

(def instrumentation-methods
  (om/specify-state-methods!
   (-> om/pure-methods
     (instrument-methods)
     (clj->js))))

(defn avg [coll]
  (/ (reduce + coll)
     (count coll)))

(defn std-dev [coll]
  (let [a (avg coll)]
    (Math/sqrt (avg (map #(Math/pow (- % a) 2) coll)))))

(defn compare-display-name [a b]
  (compare (:display-name b)
           (:display-name a)))

(defn compare-last-update [a b]
  (let [res (compare (max (:last-will-update a) (:last-will-mount a))
                     (max (:last-will-update b) (:last-will-mount b)))]
    (if (zero? res)
      (compare-display-name a b)
      res)))

(defn format-shortcut [key-set]
  (str/join "+" (sort-by (comp - count) key-set)))

(defn stats-view [data owner {:keys [clear-shortcut toggle-shortcut sort-shortcut]}]
  (reify
    om/IDisplayName (display-name [_] "Om Instrumentation")
    om/IInitState (init-state [_] {:shown? false
                                   :sort-orders (cycle [:last-update :display-name
                                                        :mount-count :render-count])})
    om/IDidMount
    (did-mount [_]
      (keyboard/register-key-handler owner {clear-shortcut #(om/transact! data (constantly {}))
                                            toggle-shortcut #(om/update-state! owner :shown? not)
                                            sort-shortcut #(om/update-state! owner :sort-orders rest)}))
    om/IWillUnmount
    (will-unmount [_] (keyboard/dispose-key-handler owner))
    om/IRenderState
    (render-state [_ {:keys [shown? sort-orders]}]
      (dom/figure nil
        (when shown?
          (let [sort-order (first sort-orders)
                stats-compare (case sort-order
                                :last-update compare-last-update
                                :display-name compare-display-name
                                (fn [x y] (compare (sort-order x) (sort-order y))))
                stats (map (fn [[display-name renders]]
                             (let [render-times (filter identity (mapcat :render-ms renders))
                                   mount-times (filter identity (mapcat :mount-ms renders))]
                               {:display-name (or display-name "Unknown")
                                :render-count (count render-times)
                                :mount-count (count mount-times)

                                :last-will-update (last (sort (map :last-will-update renders)))
                                :last-will-mount (last (sort (map :last-will-mount renders)))

                                :last-render-ms (last (:render-ms (last (sort-by :last-did-update renders))))
                                :last-mount-ms (last (:mount-ms (last (sort-by :last-did-mount renders))))

                                :average-render-ms (when (seq render-times) (int (avg render-times)))
                                :average-mount-ms (when (seq mount-times) (int (avg mount-times)))

                                :max-render-ms (when (seq render-times) (apply max render-times))
                                :max-mount-ms (when (seq mount-times) (apply max mount-times))

                                :min-render-ms (when (seq render-times) (apply min render-times))
                                :min-mount-ms (when (seq mount-times) (apply min mount-times))

                                :render-std-dev (when (seq render-times) (int (std-dev render-times)))
                                :mount-std-dev (when (seq mount-times) (int (std-dev mount-times)))}))
                           (reduce (fn [acc [display-name data]]
                                     (update-in acc [(:display-name data)] (fnil conj []) data))
                                   {} data))]
            (dom/table #js {:className "instrumentation-table"}
              (dom/thead nil
                (dom/tr nil
                  (dom/th nil "component")
                  (dom/th #js {:className "number right"} "render ")
                  (dom/th #js {:className "number left"} "/ mount")
                  (dom/th #js {:className "number" :colSpan "2"} "last-ms")
                  (dom/th #js {:className "number" :colSpan "2"} "average-ms")
                  (dom/th #js {:className "number" :colSpan "2"} "max-ms")
                  (dom/th #js {:className "number" :colSpan "2"} "min-ms")
                  (dom/th #js {:className "number" :colSpan "2"} "std-ms")))
              (apply dom/tbody nil
                     (for [{:keys [display-name
                                   last-will-update last-will-mount
                                   average-render-ms average-mount-ms
                                   max-render-ms max-mount-ms
                                   min-render-ms min-mount-ms
                                   render-std-dev mount-std-dev
                                   render-count mount-count
                                   last-render-ms last-mount-ms] :as stat}
                           (reverse (sort stats-compare stats))]
                       (dom/tr nil
                         (dom/td nil display-name)
                         (dom/td #js {:className "number" } render-count)
                         (dom/td #js {:className "number" } (when mount-count (gstring/format "%4d" mount-count)))

                         (dom/td #js {:className "number" } last-render-ms)
                         (dom/td #js {:className "number" } (when last-mount-ms (gstring/format "%3d" last-mount-ms)))

                         (dom/td #js {:className "number" } average-render-ms)
                         (dom/td #js {:className "number" } (when average-mount-ms (gstring/format "%3d" average-mount-ms)))

                         (dom/td #js {:className "number" } max-render-ms)
                         (dom/td #js {:className "number" } (when max-mount-ms (gstring/format "%3d" max-mount-ms)))

                         (dom/td #js {:className "number" } min-render-ms)
                         (dom/td #js {:className "number" } (when min-mount-ms (gstring/format "%3d" min-mount-ms)))

                         (dom/td #js {:className "number" } render-std-dev)
                         (dom/td #js {:className "number" } (when mount-std-dev (gstring/format "%3d" mount-std-dev))))))
              (dom/tfoot nil
                (dom/tr nil
                  (dom/td #js {:className "instrumentation-info" :colSpan "13"}
                          (gstring/format "Component render stats, sorted by %s (%s). Clicks go through. %s to toggle, %s to clear."
                                          sort-order
                                          (format-shortcut sort-shortcut)
                                          (format-shortcut toggle-shortcut)
                                          (format-shortcut clear-shortcut))))))))))))

(defn prepend-stats-node [classname]
  (let [node (goog.dom/htmlToDocumentFragment (gstring/format "<div class='%s'></div>" classname))
        body js/document.body]
    (.insertBefore body node (.-firstChild body))
    node))

(defn setup-component-stats!
  ([]
   (setup-component-stats! {}))
  ([{:keys [class clear-shortcut toggle-shortcut sort-shortcut]
     :or {class "om-instrumentation"
          clear-shortcut #{"shift" "ctrl" "alt" "k"}
          toggle-shortcut #{"shift" "ctrl" "alt" "j"}
          sort-shortcut #{"shift" "ctrl" "alt" "s"}}}]
   (let [stats-node (or (goog.dom/getElementByClass class)
                        (prepend-stats-node class))]
     (om/root
      stats-view
      component-stats
      {:target stats-node
       :opts {:clear-shortcut clear-shortcut
              :toggle-shortcut toggle-shortcut
              :sort-shortcut sort-shortcut}}))))


================================================
FILE: src/om_i/hacks.cljs
================================================
(ns om-i.hacks
  (:require [goog.dom :as dom]))

(def om-i-css "@keyframes in-fade-top-soft{0%{opacity:0;transform:translate3d(0, -4rem, 0)}100%{opacity:1;transform:none}}@-webkit-keyframes in-fade-top-soft{0%{opacity:0;-webkit-transform:translate3d(0, -4rem, 0)}100%{opacity:1;-webkit-transform:none}}.om-instrumentation{user-select:none;-moz-user-select:none;-webkit-user-select:none;pointer-events:none;color:#888;position:fixed;z-index:1000;top:0;left:0;width:100%}.instrumentation-table{-webkit-animation:in-fade-top-soft 500ms;animation:in-fade-top-soft 500ms;background-color:rgba(0,0,0,0.6);font-family:Monaco,monospace;font-size:.75rem;line-height:2;width:100%}.instrumentation-table th{color:#fff;line-height:1rem;padding:1.5rem 0;text-transform:uppercase;text-align:left}.instrumentation-table th:not(:first-child){text-align:center}.instrumentation-table th.left{text-align:left}.instrumentation-table th.right{text-align:right}.instrumentation-table th,.instrumentation-table td{white-space:pre-wrap}.instrumentation-table th:first-child,.instrumentation-table td:first-child{padding-left:1.5rem}.instrumentation-table tbody tr:nth-child(odd){background-color:rgba(0,0,0,0.4)}.instrumentation-table tbody td:nth-child(even){text-align:right;border-right:1px dashed;padding-right:.5em}.instrumentation-table tbody td:nth-child(odd){text-align:left;padding-left:.5em}.instrumentation-table tbody td:first-child{padding-left:1.5rem}.instrumentation-table tfoot td{line-height:1rem;text-align:center;padding:1.5rem 0}.instrumentation-table small{font-size:1em;opacity:.5}")

(defn insert-styles
  "This shouldn't be used in real code, but it can be useful when
   exploring the code for the first time. Closure will eliminate this
   function in production unless you're using it in production. You're not
   using it in production, are you?"
  []
  (let [s (goog.dom/createElement "style")]
    (goog.dom/setTextContent s om-i-css)
    (goog.dom/appendChild js/document.head s)))


================================================
FILE: src/om_i/keyboard.cljs
================================================
(ns om-i.keyboard
  (:require [goog.events]
            [om.core :as om])
  (:import [goog.ui IdGenerator]))

(def code->key
  "map from a character code (read from events with event.which)
  to a string representation of it.
  Only need to add 'special' things here."
  {  8 "backspace"
    13 "enter"
    16 "shift"
    17 "ctrl"
    18 "alt"
    27 "esc"
    33 "pageup"
    34 "pagedown"
    36 "home"
    37 "left"
    38 "up"
    39 "right"
    40 "down"
    46 "del"
    91 "meta"
    32 "space"
   186 ";"
   191 "/"
   219 "["
   221 "]"
   187 "="
   189 "-"
   190 "."
   220 "\\"})

(def mod-translation
  {"shiftKey" "shift"
   "altKey"   "alt"
   "ctrlKey"  "ctrl"
   "metaKey"  "meta"})

(defn event-modifiers
  "Given a keydown event, return the set of modifier keys that were being held."
  [e]
  (reduce (fn [acc [modifier key-name]]
            (if (aget e modifier)
              (conj acc key-name)
              acc))
          #{} mod-translation))

(defn event->key-set
  "Given an event, return a set of keys string like #{\"up\"} or #{\"shift\" \"l\"}
  describing the keys that were pressed. Will return lone modifier keys, like shift or ctrl"
  [e]
  (let [code (.-keyCode e)
        key (or (code->key code) (.toLowerCase (js/String.fromCharCode code)))]
    (conj (event-modifiers e) key)))

(defn match-keys
  "Given a keymap for the component and the most recent series of keys
  that were pressed (not the codes, but sets of keys like #{'shift' 'r'})
  return a handler fn associated with a key combo in the keys
  list or nil."
  [keymap keys]
  (->> keymap
    (keep (fn [[key-set f]]
            (when (= keys key-set) f)))
    first))

(defn register-key-handler [owner keymap]
  (om/set-state-nr! owner ::event-key
    (goog.events/listen
     js/window
     "keydown"
     (fn [e]
       (when-let [f (match-keys keymap (event->key-set e))]
         (f))))))

(defn dispose-key-handler [owner]
  (goog.events/unlistenByKey (om/get-state owner ::event-key)))
Download .txt
gitextract_8uedg_ze/

├── .gitignore
├── CHANGES.md
├── README.md
├── circle.yml
├── project.clj
├── resources/
│   ├── om-i.css
│   └── om-i.less
└── src/
    └── om_i/
        ├── core.cljs
        ├── hacks.cljs
        └── keyboard.cljs
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (25K chars).
[
  {
    "path": ".gitignore",
    "chars": 102,
    "preview": "pom.xml\n*jar\n/lib/\n/classes/\n/out/\n/target/\n.lein-deps-sum\n.lein-repl-history\n.lein-plugins/\n/script/\n"
  },
  {
    "path": "CHANGES.md",
    "chars": 515,
    "preview": "## 0.1.8\n### Changes\n* Stop depending on _rootNodeId so that we can support React 0.13.\n\n## 0.1.7\n### Changes\n* Fix orde"
  },
  {
    "path": "README.md",
    "chars": 3822,
    "preview": "# Om-instrumentation\n\nInstrumentation helper for Om applications.\n\n[![Circle CI](https://circleci.com/gh/PrecursorApp/om"
  },
  {
    "path": "circle.yml",
    "chars": 87,
    "preview": "dependencies:\n  post:\n    - lein cljsbuild once\n\ntest:\n  override:\n    - echo no tests\n"
  },
  {
    "path": "project.clj",
    "chars": 900,
    "preview": "(defproject precursor/om-i \"0.1.8\"\n  :description \"Instrumentation helpers for Om applications\"\n  :url \"https://github.c"
  },
  {
    "path": "resources/om-i.css",
    "chars": 1829,
    "preview": "@keyframes in-fade-top-soft {\n  0% {\n    opacity: 0;\n    transform: translate3d(0, -4rem, 0);\n  }\n  100% {\n    opacity: "
  },
  {
    "path": "resources/om-i.less",
    "chars": 1843,
    "preview": "@black: #000;\n@white: #fff;\n@gray:  #888;\n\n@font_mono: Monaco, monospace;\n@zindex-instrumentation: 1000;\n\n@tile: (1rem *"
  },
  {
    "path": "src/om_i/core.cljs",
    "chars": 10641,
    "preview": "(ns om-i.core\n  (:require [clojure.string :as str]\n            [goog.dom]\n            [goog.string :as gstring]\n        "
  },
  {
    "path": "src/om_i/hacks.cljs",
    "chars": 1990,
    "preview": "(ns om-i.hacks\n  (:require [goog.dom :as dom]))\n\n(def om-i-css \"@keyframes in-fade-top-soft{0%{opacity:0;transform:trans"
  },
  {
    "path": "src/om_i/keyboard.cljs",
    "chars": 1997,
    "preview": "(ns om-i.keyboard\n  (:require [goog.events]\n            [om.core :as om])\n  (:import [goog.ui IdGenerator]))\n\n(def code-"
  }
]

About this extraction

This page contains the full source code of the PrecursorApp/om-i GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (23.2 KB), approximately 6.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!