anchor.diff:              OthersTop = length(Others0)
       end,
       SemVers = lists:sort(CmpSemVers, lists:filtermap(fun get_semver/1, Tags)),
  -    tags_table(Dir, Others ++ group_by_major(SemVers, OthersTop)).
  +    table_tags(Dir, Others ++ group_by_major(SemVers, OthersTop)).
   
   group_by_major ([{SemVer,Rev}|TaggedSemVers], OthersTop) ->
       group_by_major({hd(SemVer),[Rev]}, TaggedSemVers, [], 1, OthersTop).
  
positive.code: list_titles (DocsRoot, Revs) ->
      case [list_rev(DocsRoot, Rev) || Rev <- Revs] of
          [] -> "(none)";
          Items  -> string:join(Items, " &nbsp; ")
      end.
  
  %%FIXME: escape htmlentities (and security in path names?)
  
negative.code:     "<tr>\n"
          ++ lists:flatmap(fun (TD) -> "<td>" ++ TD ++ "</td>\n" end, TDs) ++
      "</tr>\n".
  
  table_titles (Items) ->
      C = trunc(math:sqrt(length(Items))),
      Cols = split_n_times(C, Items, []),
      Body = [[case Rev of
                   '@' -> "";
                   _ -> Rev
               end || Rev <- Revs]
              || Revs <- Cols],
      "<table>\n"
          "<tbody>\n" ++ lists:flatmap(fun html_row/1, Body) ++ "</tbody>\n"
      "</table>\n".
  
  split_n_times (_, [], Acc) -> Acc;
  split_n_times (N, List, Acc)
    when length(List) >= N ->
      {Top0,Rest} = lists:split(N, List),
      Top = case length(Top0) of
                N -> Top0;
                S when S < N -> Top0 ++ lists:duplicate(N - S, '@')
            end,
      split_n_times(N, Rest, [Top|Acc]).
  
  %% http://erlang.org/pipermail/erlang-questions/2012-October/069856.html
  transpose ([[X|Xs] | Xss]) ->
  