docs: def bbl_to_cys(bblfile:str, oriented:bool=False, width_as_cover:bool=True,
                 show_cover:str='cover: {}', false_edge_on_hover:bool=True,
                 default_poweredge_width:int=5):
      """Yield lines of js to write in output file"""
      # False edges in clique
      with open(bblfile) as fd:
          falsedges = tuple(
              (src, trg) for _, src, trg in
              (l.strip().split('\t') for l in fd if l.startswith('FALSEDGE'))
          )
      if false_edge_on_hover:
          nodes_in_false_edges = set(itertools.chain.from_iterable(falsedges))
      # Detect incomplete power edges
      with open(bblfile) as fd:
          falsepoweredges = {}
          for line in fd:
              if line.startswith('FALSEPOWEREDGE'):
                  _, seta, setb, src, trg = line.strip().split('\t')
                  falsepoweredges.setdefault(frozenset((seta, setb)), set()).add((src, trg))
                  if false_edge_on_hover:
                      nodes_in_false_edges.add(src)
                      nodes_in_false_edges.add(trg)
  
      # Build node hierarchy
      tree = BubbleTree.from_bubble_file(bblfile, symmetric_edges=False, oriented=oriented)
      def isclique(node): return node in tree.edges.get(node, ())
      def handle_node(node, parent=None):
          clique = isclique(node)
          if false_edge_on_hover and clique:
              falsedges = ((src, trg) for src, trg in itertools.combinations(tuple(tree.nodes_in(node)), r=2)
                           if src in nodes_in_false_edges and trg in nodes_in_false_edges)
          else: falsedges = ()
          return JS_NODE_LINE(node, parent, clique=clique, falsedges=falsedges)
  
      yield from JS_HEADER
  
      for node in tree.roots:
          yield handle_node(node)
          yield JS_NODE_LINE(node, clique=isclique(node))
      parents = reversed_graph(tree.inclusions)  # (power)node -> direct parent
      for node, parents in parents.items():
          assert len(parents) == 1, {node: parents}
          yield handle_node(node, parent=next(iter(parents)))
  
      yield from JS_MIDDLE
  
      use_cover = width_as_cover or show_cover
      if use_cover:
          def coverof(source, target) -> int:
              source_cover = sum(1 for _ in tree.nodes_in(source))
              target_cover = sum(1 for _ in tree.nodes_in(target))
              return (source_cover or 1) * (target_cover or 1)
          def labelof(cover:int) -> str or None:
              if cover > 1:  # it's a power edge
                  return show_cover.format(cover)
      # Now, (power) edges
      powernodes = frozenset(tree.powernodes())
      for source, targets in tree.edges.items():
          for target in targets:
              if target == source:  continue  # cliques are not handled this way
              label, attrs, isreflexive = None, {}, False
              ispower = source in powernodes or target in powernodes
              if use_cover:
                  cover = coverof(source, target)
                  label, attrs = labelof(cover), {'width': 2+cover if width_as_cover else default_poweredge_width}
              if ispower and frozenset((source, target)) in falsepoweredges:
                  attrs['falsedges'] = list(map(list, falsepoweredges[frozenset((source, target))]))
              yield ' '*8 + JS_EDGE_LINE(source, target, ispower, label=label, attrs=attrs)
  
      # If asked so, add false edges in the file as regular edges
      if not false_edge_on_hover:
          for source, target in falsedges:
              yield ' '*8 + JS_FALSEDGE_LINE(source, target)
          for edges in falsepoweredges.values():
              for source, target in edges:
                  yield ' '*8 + JS_FALSEDGE_LINE(source, target)
  
      yield from JS_FOOTER
  
      if false_edge_on_hover:
          yield from JS_MOUSEOVER_SHOW_CALLBACKS
      else:
          yield from JS_MOUSEOVER_WIDTH_CALLBACKS