            (swap! state
              (fn [state]
                (-> state
-                   (update-in [::rid rid ::nav-stack] conj params)
-                   (assoc-in [::rid rid ::object] [::oid ref-oid]))))
+                   (update-in [::tap-page rid ::nav-stack] conj params)
+                   (assoc-in [::tap-page rid ::object] [::oid ref-oid]))))
+           (refresh-ui [[::tap-page rid]])
            (add-new-nav-obj state rid ref-oid))}))))
 
 (defmutation nav-stack-jump [{:keys [rid oid idx] :as params}]

  [this {::keys [rid] :as props}]
  {:ident
   (fn []
     [::rid rid])

   :initLocalState
   (fn [this _]
     (let [state-ref (atom {})]
       {:state-ref state-ref
        :scroll-ref
        (fn [^js node]
          (swap! state-ref assoc :node node)
          #_(when node
              (.addEventListener node "scroll"
                (gfn/debounce
                  (fn [e]
                    (let [max (.-scrollHeight node)
                          current (+ (.-scrollTop node) (.-offsetHeight node))

                          is-near-bottom
                          (> current (- max 25))]
                      (swap! state-ref assoc :is-near-bottom is-near-bottom)))
                  16))))}))

   ;; FIXME: I'd like to have the last tap at the bottom
   ;; auto scroll this to bottom has too many issues
   ;; so leaving it at top for now

   #_:componentDidUpdate
   #_(fn [this _ _]
       (let [state-ref (fc/get-state this :state-ref)
             {:keys [node is-near-bottom]} @state-ref]
         (when (and node is-near-bottom)
           (js/setTimeout
             (fn []
               (let [scroll-top (.-scrollHeight node)]
                 (js/console.log "setting scroll-top to" scroll-top)
                 (set! (.-scrollTop node) scroll-top)))
             ;; FIXME: do this properly
             ;; this is only required because componentDidUpdate fires
             ;; before all all the taps may have been added
             ;; it might render an empty div first because it is still
             ;; fetching the data, which means the scrollHeight is
             ;; lower than it needs to be
             ;; don't want to set a fixed height either though
             50))))

   :query
   (fn []
     [::rid
      ::runtime-info
      ::supported-ops
      {::objects (fc/get-query ObjectListItem)}
      {::object (fc/get-query ObjectDetail)}
      ::nav-stack])}

  (let [{::keys [runtime-info supported-ops objects object nav-stack]}
        props

        select-fn
        (fn [oid]
          (fc/transact! this [(select-object {:rid rid :oid oid})]))

        scroll-ref
        (fc/get-state this :scroll-ref)

  [this summary fragment]
  (render-seq this summary fragment))

(defstyled map-root :div [env]
  {:display "grid"
   :padding "0 1rem"
   :grid-template-columns "min-content minmax(25%, auto)"
   :grid-row-gap "1px"
   :grid-column-gap ".5rem"})

(defstyled map-span :div [env]
  {:grid-column-start "span 2"})

(defstyled map-entry :div [env]
  {:display "contents"
   :cursor "pointer"})

;; {:className "pl-4 pr-2 font-bold whitespace-no-wrap truncate"}
(defstyled map-key :div [env]
  {:font-weight "bold"
   :padding "0 .25rem"
   :white-space "nowrap"
   :overflow "hidden"
   :text-overflow "ellipsis"
   :background-color "#fafafa"
   "&:hover"
   {:background-color "#eee"}})

;; {:className "pr-4 truncate"}
(defstyled map-val :div [env]
  {:white-space "nowrap"
   :overflow "hidden"
   :text-overflow "ellipsis"})

(defmethod render-view :map
  [this {:keys [oid entries] :as summary} fragment]
  (map-root {:class "font-mono"}
    (html/for [idx (range entries)
               :let [{:keys [key val] :as entry} (get fragment idx)
                     [key-limit-reached key-limit-text] key]]
      (cond
        (nil? entry)
        (map-span
          {:key idx :className "border"
           :onMouseEnter
           (fn [e]
             (fc/transact! this [(request-fragment {:oid oid
                                                    :idx idx})]))}
          "not available, fetch")

        ;; started loading
        (empty? entry)
        (map-span
          {:key idx :className "border"} "...")

        ;; long key
        #_#_(or key-limit-reached
                (> (count key-limit-text) 22))
            (html/div {:key idx
                       :className "border-b hover:bg-gray-200"
                       :onClick
                       (fn [e]
                         (fc/transact! this [(nav-object {:oid oid
                                                          :idx idx
                                                          :key key})]))}
              (html/div {:className "px-4 py-1 font-bold whitespace-no-wrap cursor-pointer truncate"}
                (render-edn-limit key))
              (html/div {:className "pl-4 pr-4 py-1 cursor-pointer truncate"}
                "↳ " (render-edn-limit val)))

        :else
        (map-entry {:key idx
                    :onClick
                    (fn [e]
                      (fc/transact! this [(nav-object {:oid oid
                                                       :idx idx
                                                       :key key})]))}
          (map-key
            (render-edn-limit key))
          (map-val
            (render-edn-limit val)))
        ))))

(defsc Runtime [this {::keys [rid] :as props}]
  {:ident
