anchor.diff:  gui_max(BL) when BL < 10 -> 30;
   gui_max(BL) -> 3 * BL.
   
  -gui_response(_Tab,ReplyTo,Gres) ->
  -    ReplyTo ! Gres.
  -
  -gui_nop(Tab,GuiResult,#state{bufBot=BufBot,rawTop=RawTop,rawBot=RawBot,replyTo=ReplyTo}=State) -> 
  +gui_response(Gres0, #state{nav=raw,rawCnt=RawCnt,replyTo=ReplyTo}=State) ->
  +    Gres1 = Gres0#gres{cnt=RawCnt,toolTip=integer_to_list(RawCnt)},
  +    ReplyTo ! Gres1,
  +    % ?Log("ReplyTo  ~p~n", [ReplyTo]),
  +    ?Log("Gres  ~p~n", [Gres1]),
  +    State;
  +gui_response(Gres0, #state{nav=ind,rawCnt=RawCnt,indCnt=IndCnt,guiCol=true,replyTo=ReplyTo}=State) ->
  +    ToolTip = integer_to_list(RawCnt) ++ [$/] ++ integer_to_list(IndCnt) ++ " page needs refresh",
  +    Gres1 = Gres0#gres{cnt=IndCnt,toolTip=ToolTip},
  +    ReplyTo ! Gres1,
  +    % ?Log("ReplyTo  ~p~n", [ReplyTo]),
  +    ?Log("Gres  ~p~n", [Gres1]),
  +    State;
  +gui_response(Gres, #state{nav=ind,rawCnt=RawCnt,indCnt=IndCnt,replyTo=ReplyTo}=State) ->
  +    ToolTip = integer_to_list(RawCnt) ++ [$/] ++ integer_to_list(IndCnt),
  +    Gres1 = Gres#gres{cnt=IndCnt,toolTip=ToolTip},
  +    ReplyTo ! Gres1,
  +    % ?Log("ReplyTo  ~p~n", [ReplyTo]),
  +    ?Log("Gres  ~p~n", [Gres1]),
  +    State.
  +
  +gui_close(GuiResult,State) -> 
  +    ?Log("gui_close () ~p~n", [GuiResult#gres.state]),
  +    gui_response(GuiResult#gres{operation=close},State).
  +
  +gui_nop(GuiResult,State) -> 
       ?Log("gui_nop () ~p ~p~n", [GuiResult#gres.state, GuiResult#gres.loop]),
  -    gui_response(Tab,ReplyTo,GuiResult#gres{bufBot=BufBot,rawTop=RawTop,rawBot=RawBot}),
  -    State.
  -
  -gui_replace(Tab,GuiTop,GuiBot,GuiResult,#state{bufBot=BufBot,replyTo=ReplyTo}=State) ->
  -    ?Log("gui_replace (~p ~p) ~p ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  -    Rows=gui_rows(Tab,GuiTop,GuiBot,State),
  -    gui_response(Tab,ReplyTo,GuiResult#gres{operation=rpl,rows=Rows,bufBot=BufBot,rawTop=GuiTop,rawBot=GuiBot}),
  -    State#state{rawTop=GuiTop,rawBot=GuiBot}.
  -
  -gui_prepend(Tab,GuiTop,GuiBot,GuiResult,#state{gl=GL,bufBot=BufBot,rawTop=RawTop,rawBot=RawBot,replyTo=ReplyTo}=State) ->
  -    case RawTop-1 of
  -        GuiBot ->       ?Log("gui_prepend (~p ~p) ~p ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop]);
  -        GB ->           ?Log("gui_prepend (~p ~p) ~p ~p EXPECTED RawBot ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop, GB])
  -    end,
  -    NewRawBot = if 
  -        (RawBot >= GuiTop+GL) ->        GuiTop+GL-1;
  -        true ->                         RawBot
  -    end,
  -    Rows=gui_rows(Tab,GuiTop,GuiBot,State),
  -    gui_response(Tab,ReplyTo,GuiResult#gres{operation=prp,rows=Rows,bufBot=BufBot,rawTop=GuiTop,rawBot=NewRawBot}),
  -    State#state{rawTop=GuiTop,rawBot=NewRawBot}.
  -
  -gui_append(Tab,GuiTop,GuiBot,GuiResult,#state{gl=GL,bufBot=BufBot,rawTop=RawTop,rawBot=RawBot,replyTo=ReplyTo}=State) ->
  -    case RawBot+1 of
  -        GuiTop ->   ?Log("gui_append (~p ~p) ~p ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop]);
  -        GT ->       ?Log("gui_append (~p ~p) ~p ~p EXPECTED RawTop ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop, GT])
  -    end,
  -    NewRawTop = if 
  -        (RawTop =< GuiBot-GL) ->        GuiBot-GL+1;
  -        true ->                         RawTop
  -    end,
  -    Rows=gui_rows(Tab,GuiTop,GuiBot,State),
  -    gui_response(Tab,ReplyTo,GuiResult#gres{operation=app,rows=Rows,bufBot=BufBot,rawTop=NewRawTop,rawBot=GuiBot}),
  -    State#state{rawTop=NewRawTop,rawBot=GuiBot}.
  -
  -gui_rows(_Tab,Top,Bottom,#state{rowFun=RowFun,tableId=TableId}) ->
  -    % ?Log("RowFun ~p~n", [RowFun]),
  -    Rows = ets:select(TableId,[{'$1',[{'>=',{element,1,'$1'},Top},{'=<',{element,1,'$1'},Bottom}],['$_']}]),
  -    [gui_row_expand(R, TableId, RowFun) || R <- Rows].
  +    gui_response(GuiResult#gres{operation=nop},State).
  +
  +gui_clear(GuiResult,State0) ->
  +    ?Log("gui_clear () ~p ~p~n", [GuiResult#gres.state, GuiResult#gres.loop]),
  +    Default = #state{}, 
  +    State1 = State0#state{ guiCnt=Default#state.guiCnt, guiTop=Default#state.guiTop         
  +                         , guiBot=Default#state.guiBot, guiCol=Default#state.guiCol},
  +    gui_response(GuiResult#gres{operation=clr,keep=0}, State1).
  +
  +gui_replace(Top,Bot,GuiResult,State0) ->
  +    ?Log("gui_replace ~p to ~p ~p ~p~n", [Top, Bot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    Rows=all_rows(Top,Bot,State0),
  +    Cnt=length(Rows),
  +    gui_response(GuiResult#gres{operation=rpl,rows=Rows,keep=Cnt}
  +        ,State0#state{guiCnt=Cnt,guiTop=Top,guiBot=Bot,guiCol=false}).
  +
  +gui_replace_from(Top,Limit,GuiResult,#state{nav=raw,tableId=TableId,rowFun=RowFun}=State0) ->
  +    Ids= [Top | ids_after(Top, Limit-1, State0)],
  +    Cnt = length(Ids),
  +    Rows = rows_for_ids(Ids,TableId,RowFun),
  +    NewGuiTop = Top,
  +    NewGuiBot = lists:last(Ids),
  +    ?Log("gui_replace_from  ~p to ~p ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=rpl,rows=Rows,keep=Cnt}
  +        ,State0#state{guiCnt=Cnt,guiTop=NewGuiTop,guiBot=NewGuiBot});
  +gui_replace_from(Top,Limit,GuiResult,#state{nav=ind,tableId=TableId}=State0) ->
  +    Keys = [Top | keys_after(Top, Limit-1, State0)],
  +    Cnt = length(Keys),
  +    Rows = rows_for_keys(Keys,TableId),
  +    NewGuiTop = Top,
  +    NewGuiBot = lists:last(Keys),
  +    ?Log("gui_replace_from  ~p to ~p ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=rpl,rows=Rows,keep=Cnt}
  +        ,State0#state{guiCnt=Cnt,guiTop=NewGuiTop,guiBot=NewGuiBot}).
  +
  +gui_replace_until(Bot,Limit,GuiResult,#state{nav=raw,tableId=TableId,rowFun=RowFun}=State0) ->
  +    Ids = ids_before(Bot, Limit-1, State0) ++ [Bot],
  +    Cnt = length(Ids),
  +    Rows = rows_for_ids(Ids,TableId,RowFun),
  +    NewGuiTop = hd(Ids),
  +    NewGuiBot = Bot,
  +    ?Log("gui_replace_until  ~p to ~p ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=rpl,rows=Rows,keep=Cnt}
  +        ,State0#state{guiCnt=Cnt,guiTop=NewGuiTop,guiBot=NewGuiBot});
  +gui_replace_until(Bot,Limit,GuiResult,#state{nav=ind,tableId=TableId}=State0) ->
  +    Keys = keys_before(Bot, Limit-1, State0) ++ [Bot],
  +    Cnt = length(Keys),
  +    Rows = rows_for_keys(Keys,TableId),
  +    NewGuiTop = hd(Keys),
  +    NewGuiBot = Bot,
  +    ?Log("gui_replace_until  ~p to ~p ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=rpl,rows=Rows,keep=Cnt}
  +        ,State0#state{guiCnt=Cnt,guiTop=NewGuiTop,guiBot=NewGuiBot}).
  +
  +gui_prepend(GuiResult,#state{nav=raw,bl=BL,gl=GL,guiCnt=GuiCnt,guiTop=GuiTop}=State0) ->
  +    Rows = rows_before(GuiTop, BL, State0),
  +    Cnt = length(Rows),
  +    IdsKept = ids_after(GuiTop,min(GuiCnt,GL-Cnt-1),State0),
  +    NewGuiCnt = length(IdsKept)+1+Cnt,
  +    NewGuiTop = hd(hd(Rows)),
  +    NewGuiBot = lists:last(IdsKept),
  +    ?Log("gui_prepend (~p ~p) ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=prp,rows=Rows,keep=NewGuiCnt}
  +        ,State0#state{guiCnt=NewGuiCnt,guiTop=NewGuiTop,guiBot=NewGuiBot});
  +gui_prepend(GuiResult,#state{nav=ind,bl=BL,gl=GL,tableId=TableId,guiCnt=GuiCnt,guiTop=GuiTop}=State0) ->
  +    Keys=keys_before(GuiTop, BL, State0),
  +    Cnt = length(Keys),
  +    Rows = rows_for_keys(Keys,TableId),
  +    KeysKept = keys_after(GuiTop,min(GuiCnt,GL-Cnt-1),State0),
  +    NewGuiCnt = length(KeysKept)+1+Cnt,
  +    NewGuiTop = hd(Keys),
  +    NewGuiBot = lists:last(KeysKept),
  +    ?Log("gui_prepend (~p ~p) ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=prp,rows=Rows,keep=NewGuiCnt}
  +        ,State0#state{guiCnt=NewGuiCnt,guiTop=NewGuiTop,guiBot=NewGuiBot}).
  +
  +gui_append(GuiResult,#state{nav=raw,bl=BL,gl=GL,guiCnt=GuiCnt,guiBot=GuiBot}=State0) ->
  +    Rows=rows_after(GuiBot, BL, State0),
  +    Cnt = length(Rows),
  +    IdsKept = ids_before(GuiBot,min(GuiCnt,GL-Cnt-1),State0),
  +    NewGuiCnt = length(IdsKept)+1+Cnt,
  +    NewGuiTop = hd(IdsKept),
  +    NewGuiBot = hd(lists:last(Rows)),
  +    ?Log("gui_append  (~p ~p) ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=app,rows=Rows,keep=NewGuiCnt}
  +        ,State0#state{guiCnt=NewGuiCnt,guiTop=NewGuiTop,guiBot=NewGuiBot});
  +gui_append(GuiResult,#state{nav=ind,bl=BL,gl=GL,tableId=TableId,guiCnt=GuiCnt,guiBot=GuiBot}=State0) ->
  +    Keys=keys_after(GuiBot, BL, State0),
  +    Cnt = length(Keys),
  +    Rows = rows_for_keys(Keys,TableId),
  +    KeysKept = keys_before(GuiBot,min(GuiCnt,GL-Cnt-1),State0),
  +    NewGuiCnt = length(KeysKept)+1+Cnt,
  +    NewGuiTop = hd(KeysKept),
  +    NewGuiBot = lists:last(Keys),
  +    ?Log("gui_append  (~p ~p) ~p ~p~n", [NewGuiTop, NewGuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(GuiResult#gres{operation=app,rows=Rows,keep=NewGuiCnt}
  +        ,State0#state{guiCnt=NewGuiCnt,guiTop=NewGuiTop,guiBot=NewGuiBot}).
  +
  +
  +serve_empty(SN,true,State0) ->
  +    State1 = prefetch(SN,State0),          %% only when filling
  +    gui_nop(#gres{state=SN,beep=true},State1);
  +serve_empty(SN,false,State0) ->
  +    State1 = prefetch(SN,State0),          %% only when filling
  +    gui_nop(#gres{state=SN},State1).
  +
  +serve_top(SN,#state{bl=BL,bufCnt=BufCnt,bufTop=BufTop}=State0) ->
  +    if
  +        (BufCnt == 0) ->
  +            %% no data, serve empty page
  +            serve_empty(SN,false,State0);
  +        (BufCnt >= BL+BL) ->
  +            %% enough data, serve it, no need for prefetch
  +            gui_replace_from(BufTop,BL,#gres{state=SN},State0);
  +        true ->
  +            %% we have data but may need to prefetch
  +            State1 = prefetch(SN,State0),          %% only when filling
  +            gui_replace_from(BufTop,BL,#gres{state=SN},State1)
  +    end.
  +
  +serve_fwd(SN,#state{bl=BL,bufCnt=BufCnt,bufBot=BufBot,guiCnt=GuiCnt,guiBot=GuiBot,guiCol=GuiCol}=State0) ->
  +    if
  +        (BufCnt == 0) ->
  +            %% no data, serve empty gui
  +            serve_empty(SN,false,State0);
  +        (GuiCnt == 0) ->
  +            %% (re)initialize buffer
  +            serve_top(SN,State0);
  +        (GuiCol == true) andalso (GuiBot == BufBot) ->
  +            %% index view is stale at end of buffer, auto-refresh
  +            gui_replace_until(GuiBot,BL,#gres{state=SN},State0#state{guiCol=false});
  +        (GuiCol == true)  ->
  +            %% index view is stale replace next page
  +            gui_replace_from(GuiBot,BL,#gres{state=SN},State0#state{guiCol=false});
  +        (GuiBot == BufBot) ->
  +            %% we are at the end of the buffer, cannot go forward
  +            State1 = prefetch(SN,State0),          %% only when filling
  +            gui_nop(#gres{state=SN,beep=true},State1);
  +        true ->
  +            State1 = prefetch(SN,State0),          %% only when filling
  +            gui_append(#gres{state=SN},State1)
  +    end.
  +
  +serve_bwd(SN,#state{bl=BL,bufCnt=BufCnt,guiCnt=GuiCnt,guiTop=GuiTop}=State0) ->
  +    if
  +      (BufCnt == 0) ->
  +          serve_empty(SN,true,State0);
  +      (GuiCnt == 0) ->
  +          serve_top(SN,State0);
  +      true ->
  +          gui_prepend(#gres{state=SN},State0)
  +    end.
  +
  +serve_ffwd(SN,#state{bl=BL,rawBot=RawBot,guiBot=GuiBot,replyTo=ReplyTo}=State0) ->
  +    if
  +      (RawBot == 0) ->
  +          serve_empty(SN,false,State0);
  +      (RawBot =< BL) ->
  +          serve_fwd(SN,State0);
  +      (GuiBot =< BL) ->
  +          serve_fwd(SN,State0);
  +      (GuiBot+GuiBot+BL =< RawBot) ->
  +          gui_replace(GuiBot+GuiBot-BL+1,GuiBot+GuiBot,#gres{state=SN},State0);
  +      (GuiBot+GuiBot =< RawBot) ->
  +          State1 = prefetch(SN,State0),          %% only when filling
  +          gui_replace(GuiBot+GuiBot-BL+1,GuiBot+GuiBot,#gres{state=SN},State1);
  +      true ->
  +          State1 = prefetch(SN,State0),          %% only when filling
  +          State1#state{stack={button,GuiBot+GuiBot,ReplyTo}}
  +    end.
  +
  +serve_fbwd(SN,#state{bl=BL,rawBot=RawBot,guiTop=GuiTop}=State0) ->
  +    if
  +      (RawBot == 0) ->
  +          serve_empty(SN,true,State0);
  +      (GuiTop >= BL+BL) and (GuiTop div 2+BL == GuiTop-1)  ->
  +          gui_prepend(#gres{state=SN},State0);
  +      (GuiTop >= BL+BL) ->
  +          gui_replace(GuiTop div 2+1,GuiTop div 2+BL,#gres{state=SN},State0);
  +      true ->
  +          serve_top(SN,State0)
  +    end.
  +
  +serve_target(SN,Target,#state{bl=BL,rawBot=RawBot,guiTop=GuiTop,guiBot=GuiBot,replyTo=ReplyTo}=State0) ->
  +    if
  +      (RawBot == 0) and (SN == complete) ->
  +          serve_empty(SN,true,State0);
  +      (RawBot == 0) ->         
  +          %% no_data now, com back later 
  +          State1 = prefetch(SN,State0),          %% only when filling
  +          % gui_nop(#gres{state=SN,loop=Target},State1);
  +          State1#state{stack={button,Target,ReplyTo}};
  +      (Target =< 0) and (RawBot+Target > 0) ->
  +          %% target given relative to RawBot, retry with absolute target position 
  +          serve_target(SN,Target,State0);
  +      (Target =< 0)  ->
  +          serve_top(SN,State0);
  +      (Target =< RawBot) and (RawBot < BL) ->
  +          serve_top(SN,State0);
  +      (Target =< RawBot) and (GuiTop > Target) ->
  +          %% serve block backward
  +          gui_replace(Target,Target+BL-1,#gres{state=SN},State0);
  +      (Target =< RawBot) and (GuiBot < Target) and (Target > RawBot-BL-BL) ->
  +          %% serve block forward
  +          State1 = prefetch(SN,State0),          %% only when filling
  +          gui_replace(Target-BL+1,Target,#gres{state=SN},State1);
  +      (Target =< RawBot) and (GuiBot < Target) ->
  +          %% serve block forward
  +          gui_replace(Target-BL+1,Target,#gres{state=SN},State0);
  +      (Target > RawBot) and (SN == completed) ->
  +          serve_bot(SN,undefined,State0);
  +      (Target > RawBot) ->
  +          State1 = prefetch(SN,State0),          %% only when filling
  +          gui_nop(#gres{state=SN,loop=Target},State1);
  +      true ->
  +          %% target should be in GUI already
  +          gui_nop(#gres{state=SN,message="target row already in gui"},State0)
  +    end.
  +
  +
  +    % State2 = if
  +    %     (RawBot == GuiBot) ->
  +    %         State1#state{stack=Cmd}; 
  +    %     (GuiBot >= RawBot-GL) ->
  +    %         gui_append(#gres{state=tailing,loop='>|...'},State1); 
  +    %     true ->
  +    %         gui_replace_until(RawBot,BL,#gres{state=tailing,loop='>|...'},State1)
  +    % end,
  +    % %% -------------------------
  +
  +serve_bot(SN, Loop, #state{nav=Nav,bl=BL,gl=GL,bufCnt=BufCnt,bufBot=BufBot,guiCnt=GuiCnt,guiBot=GuiBot,guiCol=GuiCol}=State0) ->
  +    ?Log("serve_bot  (~p ~p) ~p ~p~n", [SN, Loop, GuiBot, BufBot]),
  +    if
  +        (BufCnt == 0) ->
  +            %% no data, serve empty
  +            serve_empty(#gres{state=SN,loop=Loop},false,State0);         
  +        (GuiCnt == 0) ->
  +            %% uninitialized view, must refresh    
  +            gui_replace_until(BufBot,BL,#gres{state=SN,loop=Loop},State0); 
  +        (GuiCol == true) ->
  +            %% dirty index view, must refresh anyways    
  +            gui_replace_until(BufBot,BL,#gres{state=SN,loop=Loop},State0); 
  +        (GuiBot == BufBot) ->
  +            %% gui is already there, noting to do       
  +            gui_nop(#gres{state=SN,loop=Loop},State0); 
  +        (Nav == raw) andalso (GuiBot < BufBot-GL) ->
  +            %% uninitialized view, must refresh    
  +            gui_replace_until(BufBot,BL,#gres{state=SN,loop=Loop},State0); 
  +        (Loop == '>|...') andalso (SN == tailing) ->
  +            %% tailing should append (don't call this far from bottom of big buffer)                 
  +            gui_append(#gres{state=SN,loop=Loop},State0); 
  +        true ->
  +            %% jump to end and discard other cases (avoid scrolling big buffer)                 
  +            gui_replace_until(BufBot,BL,#gres{state=SN,loop=Loop},State0)
  +    end.
  +
  +serve_stack( _, #state{stack=undefined}=State) -> 
  +    % no stack, nothing to do
  +    State;
  +serve_stack( _, #state{nav=ind,bufBot=B,guiBot=B}=State) -> 
  +    % gui is current at the end of the boffer, no new interesting data, nothing to do
  +    State;
  +serve_stack(SN, #state{stack={button,'>',RT}}=State0) ->
  +    % deferred forward can be executed now
  +    State1 = serve_fwd(SN,State0#state{replyTo=RT}),
  +    State1#state{stack=undefined};
  +serve_stack(SN, #state{bufCnt=BufCnt,stack={button,Target,RT}}=State0) when is_integer(Target), (BufCnt>=Target) ->
  +    % deferred target can be executed now
  +    State1 = serve_target(SN,Target,State0#state{replyTo=RT}),
  +    State1#state{stack=undefined};
  +serve_stack(completed, #state{stack={button,_,RT}}=State0) ->
  +    % deferred button can be executed now (one of the forward buttons '>' '>>' '>|' '>|...')
  +    State1 = serve_bot(complete,undefined,State0#state{replyTo=RT}),
  +    State1#state{stack=undefined};
  +serve_stack(tailing, #state{stack={button,'>|',RT}}=State0) ->
  +    % deferred button can be executed now
  +    State1 = serve_bot(tailing,undefined,State0#state{replyTo=RT}),
  +    State1#state{stack=undefined};
  +serve_stack(tailing, #state{bufBot=BufBot,guiBot=GuiBot,stack={button,'>|...',RT}}=State0) ->
  +    if
  +        (BufBot == GuiBot) -> 
  +            % no new data, nothing to do, keep stack
  +            State0; 
  +        true ->
  +            % serve new data at the bottom of the buffer, ask client to come back
  +            State1 = serve_bot(tailing,'>|...',State0#state{replyTo=RT}),
  +            State1#state{stack=undefined}
  +    end;
  +serve_stack(_ , State) -> State.
  +
  +
  +all_rows(Top, Bot, _) when Top > Bot -> [];
  +all_rows(Top, Bot, #state{nav=raw,rowFun=RowFun,tableId=TableId}) ->
  +    Rows = ets:select(TableId,[{'$1',[{'>=',{element,1,'$1'},Top},{'=<',{element,1,'$1'},Bot}],['$_']}]),
  +    [gui_row_expand(R, TableId, RowFun) || R <- Rows];
  +all_rows(Top, Bot, #state{nav=ind,indexId=IndexId,tableId=TableId}) ->
  +    IndRows = ets:select(IndexId,[{'$1',[{'>=',{element,1,'$1'},Top},{'=<',{element,1,'$1'},Bot}],['$_']}]),
  +    [gui_row_as_list(ets:lookup(TableId, Id)) || {_,Id} <- IndRows].
  +
  +rows_after(_, [], _) -> [];
  +rows_after(Key, Limit, #state{nav=raw,rowFun=RowFun,tableId=TableId}) ->
  +    case ets:select(TableId,[{'$1',[{'>',{element,1,'$1'},Key}],['$_']}],Limit) of
  +        {Rs, _Cont} ->      [gui_row_expand(R, TableId, RowFun) || R <- Rs];  
  +        '$end_of_table' ->  []
  +    end.
  +
  +rows_for_keys([],_) -> [];
  +rows_for_keys(Keys,TableId) ->
  +    [gui_row_as_list(hd(ets:lookup(TableId, Id))) || {_,Id} <- Keys].
  +
  +rows_for_ids([],_,_) -> [];
  +rows_for_ids(Ids,TableId,RowFun) ->
  +    [gui_row_expand(hd(ets:lookup(TableId, Id)), TableId, RowFun) || Id <- Ids].
  +
  +keys_before(_, 0, _) -> [];
  +keys_before(Key, Limit, #state{nav=ind,indexId=IndexId}) ->
  +    case ets:select_reverse(IndexId,[{'$1',[{'<',{element,1,'$1'},{const,Key}}],[{element,1,'$1'}]}],Limit) of
  +        {Keys, _Cont} ->    lists:reverse(Keys);  
  +        '$end_of_table' ->  []
  +    end.
  +
  +keys_after(_, 0, _) -> [];
  +keys_after(Key, Limit, #state{nav=ind,indexId=IndexId}) ->
  +    case ets:select(IndexId,[{'$1',[{'>',{element,1,'$1'},{const,Key}}],[{element,1,'$1'}]}],Limit) of
  +        {Keys, _Cont} ->    Keys;  
  +        '$end_of_table' ->  []
  +    end.
  +
  +rows_before(_, 0, _) -> [];
  +rows_before(Key, Limit, #state{nav=raw,rowFun=RowFun,tableId=TableId}) ->
  +    case ets:select_reverse(TableId,[{'$1',[{'<',{element,1,'$1'},Key}],['$_']}],Limit) of
  +        {Rs, _Cont} ->      [gui_row_expand(R, TableId, RowFun) || R <- lists:reverse(Rs)];  
  +        '$end_of_table' ->  []
  +    end;
  +rows_before(Key, Limit, #state{tableId=TableId}=State) ->
  +    Keys = keys_before(Key, Limit, State),
  +    [gui_row_as_list(ets:lookup(TableId, Id)) || {_,Id} <- Keys].
  +
  +ids_before(_, 0, _) -> [];
  +ids_before(Id, Limit, #state{nav=raw,tableId=TableId}) ->
  +    case ets:select_reverse(TableId,[{'$1',[{'<',{element,1,'$1'},Id}],[{element,1,'$1'}]}],Limit) of
  +        {Ids, _Cont} ->     lists:reverse(Ids);  
  +        '$end_of_table' ->  []
  +    end.
  +
  +ids_after(_, 0, _) -> [];
  +ids_after(Id, Limit, #state{nav=raw,tableId=TableId}) ->
  +    case ets:select(TableId,[{'$1',[{'>',{element,1,'$1'},Id}],[{element,1,'$1'}]}],Limit) of
  +        {Ids, _Cont} ->     Ids;  
  +        '$end_of_table' ->  []
  +    end.
  +
  +
  +gui_row_as_list(FullRowTuple) ->
  +    List = tuple_to_list(FullRowTuple),
  +    [hd(List),lists:nth(2,List)|lists:nthtail(3,List)].
   
   gui_row_expand({I,Op,RK}, TableId, RowFun) ->
       Row = RowFun(RK),
       ets:insert(TableId, list_to_tuple([I, Op, RK | Row])),
  -    [integer_to_list(I),Op|Row];
  +    [I,Op|Row];
   gui_row_expand(FullRowTuple, _TableId, _RowFun) ->
       List = tuple_to_list(FullRowTuple),
  -    [integer_to_list(hd(List)),lists:nth(2,List)|lists:nthtail(3,List)].
  -
  -
  -    % lists:foldl(
  -    %     fun ([I,Op,RK],Rws) when is_integer(I) ->
  -    %             Row = F(RK),
  -    %             ets:insert(TableId, list_to_tuple([I, Op, RK | Row])),
  -    %             Rws ++ [[integer_to_list(I),Op|Row]];
  -    %         ([I,_Op,_RK|Rest],Rws) when is_integer(I) ->
  -    %             [[[integer_to_list(I),Op|Rest]]|Rws] 
  -    %     end
  -    %     , []
  -    %     , Rows
  -    % ).
  -
  -clear_data(#state{tableId=TableId}=State) -> 
  -    ?Log("clear_data~n", []),
  +    [hd(List),lists:nth(2,List)|lists:nthtail(3,List)].
  +
  +raw_row_expand({I,Op,RK}, RowFun) ->
  +    list_to_tuple([I, Op, RK | RowFun(RK)]).
  +
  +data_clear(State) -> 
  +    gui_clear(ind_clear(raw_clear(State))).
  +
  +raw_clear(#state{tableId=TableId}=State) -> 
  +    ?Log("raw_clear~n", []),
       true = ets:delete_all_objects(TableId),    
  -    State#state{  bufTop = 0          
  -                , bufBot = 0          
  -                , dirtyTop = 99999999 
  -                , dirtyBot = 0        
  -                , dirtyCount = 0     
  -                , rawTop = 0         
  -                , rawBot = 0         
  +    Default = #state{}, 
  +    set_buf_counters(State#state{ rawCnt = Default#state.rawCnt
  +                                , rawTop = Default#state.rawTop          
  +                                , rawBot = Default#state.rawBot          
  +                                , dirtyCnt = Default#state.dirtyCnt
  +                                , dirtyTop = Default#state.dirtyTop 
  +                                , dirtyBot = Default#state.dirtyBot        
  +                    }). 
  +
  +ind_clear(#state{indexId=IndexId}=State) -> 
  +    ?Log("ind_clear~n", []),
  +    true = ets:delete_all_objects(IndexId),    
  +    Default = #state{}, 
  +    set_buf_counters(State#state{ indCnt = Default#state.indCnt
  +                                , indTop = Default#state.indTop                
  +                                , indBot = Default#state.indBot
  +                    }). 
  +
  +gui_clear(State) -> 
  +    ?Log("gui_clear~n", []),
  +    Default = #state{}, 
  +    State#state{  guiCnt = Default#state.guiCnt
  +                , guiTop = Default#state.guiTop         
  +                , guiBot = Default#state.guiBot         
  +                , guiCol = Default#state.guiCol         
                   }. 
   
  -append_data(SN, {[],_Complete},State) -> 
  +data_append(SN, {[],_Complete},#state{nav=Nav,rawBot=RawBot}=State) -> 
       NewPfc=State#state.pfc-1,
  -    case NewPfc of
  -        0 ->    ?Log("append_data -- count ~p~n", [0]);
  -        _ ->    ?Log("append_data -- count ~p PendingFetchCount ~p~n", [0, NewPfc])
  -    end,
  +    ?Log("data_append -~p- count ~p bufBottom ~p pfc ~p~n", [Nav,0,RawBot,NewPfc]),
       serve_stack(SN, State#state{pfc=NewPfc});
  -append_data(SN, {Recs,_Complete},#state{tableId=TableId,bufBot=BufBot}=State) ->
  +data_append(SN, {Recs,_Complete},#state{nav=raw,tableId=TableId,rawCnt=RawCnt,rawTop=RawTop,rawBot=RawBot}=State) ->
       NewPfc=State#state.pfc-1,
  -    Count = length(Recs),
  -    NewBufBot = BufBot+Count,
  -    case NewPfc of
  -        0 ->    ?Log("append_data -- count ~p bufBottom ~p~n", [Count,NewBufBot]);
  -        _ ->    ?Log("append_data -- count ~p bufBottom ~p pfc ~p~n", [Count,NewBufBot,NewPfc])
  -    end,
  -    ets:insert(TableId, [list_to_tuple([I,nop|[R]])||{I,R}<-lists:zip(lists:seq(BufBot+1, NewBufBot), Recs)]),
  -    %% ToDo: insert into filter/sort table
  -    serve_stack(SN, State#state{pfc=NewPfc,bufBot=NewBufBot}).
  -
  -update_data(Tab,SN,ChangeList,State0) ->
  -    State1 = update_data_rows(ChangeList,State0),
  -    gui_nop(Tab,#gres{state=SN},State1).  %% ToDo: return list of Ids chosen by fsm for inserts
  -
  -update_data_rows([], State) -> State;
  -update_data_rows([Ch|ChangeList], State0) ->
  -    State1 = update_data_row(Ch, State0),
  -    update_data_rows(ChangeList, State1).
  -
  -update_data_row([undefined,Fields], #state{tableId=TableId,bufBot=BufBot,dirtyTop=DT0,dirtyCount=DC0}=State0) ->
  -    Id = BufBot+1,          %% ToDo: map Fields to complete rows ("" for undefined fields)
  +    Cnt = length(Recs),
  +    NewRawCnt = RawCnt+Cnt,
  +    NewRawTop = min(RawTop,RawBot+1),   % initialized to 1 and then changed only in delete or clear
  +    NewRawBot = RawBot+Cnt,
  +    ?Log("data_append -raw- count ~p bufBottom ~p pfc ~p~n", [Cnt,NewRawBot,NewPfc]),
  +    ets:insert(TableId, [list_to_tuple([I,nop|[R]])||{I,R}<-lists:zip(lists:seq(RawBot+1, NewRawBot), Recs)]),
  +    serve_stack(SN, set_buf_counters(State#state{pfc=NewPfc,rawCnt=NewRawCnt,rawTop=NewRawTop,rawBot=NewRawBot}));
  +data_append(SN, {Recs,_Complete},#state{nav=ind,tableId=TableId,indexId=IndexId
  +        ,rawCnt=RawCnt,rawTop=RawTop,rawBot=RawBot,indCnt=IndCnt
  +        ,guiTop=GuiTop,guiBot=GuiBot,guiCol=GuiCol
  +        ,rowFun=RowFun,filterFun=FilterFun,sortFun=SortFun}=State) ->
  +    NewPfc=State#state.pfc-1,
  +    Cnt = length(Recs),
  +    NewRawCnt = RawCnt+Cnt,
  +    NewRawTop = min(RawTop,RawBot+1),   % initialized to 1 and then changed only in delete or clear
  +    NewRawBot = RawBot+Cnt,
  +    RawRows = [raw_row_expand({I,nop,RK}, RowFun) || {I,RK} <- lists:zip(lists:seq(RawBot+1, NewRawBot), Recs)],
  +    ets:insert(TableId, RawRows),
  +    IndRows = [{{SortFun(element(3,R)),element(1,R)},element(1,R)} || R <- lists:filter(FilterFun,RawRows)],
  +    % ?Log("data_append -IndRows- ~p~n", [IndRows]),
  +    FunCol = fun({X,_},{IT,IB,C}) ->  {IT,IB,(C orelse ((X>IT) and (X<IB)))}  end, 
  +    {_,_,Collision} = lists:foldl(FunCol, {GuiTop, GuiBot, false}, IndRows),    %% detect data collisions with gui content
  +    ets:insert(IndexId, IndRows),
  +    NewIndCnt = IndCnt + length(IndRows),
  +    NewIndTop = ets:first(IndexId),
  +    NewIndBot = ets:last(IndexId),
  +    NewGuiCol = (GuiCol or Collision),    
  +    ?Log("data_append -ind- count ~p bufBottom ~p pfc=~p stale=~p~n", [Cnt,NewRawBot,NewPfc,NewGuiCol]),
  +    serve_stack(SN, set_buf_counters(State#state{ pfc=NewPfc
  +                                                , rawCnt=NewRawCnt,rawTop=NewRawTop,rawBot=NewRawBot
  +                                                , indCnt=NewIndCnt,indTop=NewIndTop,indBot=NewIndBot
  +                                                , guiCol=NewGuiCol}
  +                                    )
  +                ).
  +
  +data_update(SN,ChangeList,State0) ->
  +    State1 = data_update_rows(ChangeList,State0),
  +    %% ToDo: return list of Ids chosen by fsm for inserts
  +    gui_nop(#gres{state=SN},State1).  
  +
  +data_commit(SN, #state{guiTop=GuiTop,guiBot=GuiBot}=State) -> 
  +    %% ToDo: recalculate dirty rows using KeyUpdate
  +    %%       serve errors if present (no matter the size)
  +    gui_replace(GuiTop, GuiBot, #gres{state=SN},State). 
  +
  +data_rollback(SN, #state{guiTop=GuiTop,guiBot=GuiBot}=State) -> 
  +    %% ToDo: recalculate dirty rows using Keys in buffer
  +    %%       serve errors if present (no matter the size)
  +    gui_replace(GuiTop, GuiBot, #gres{state=SN},State).  
  +
  +data_update_rows([], State) -> State;
  +data_update_rows([Ch|ChangeList], State0) ->
  +    State1 = data_update_row(Ch, State0),
  +    data_update_rows(ChangeList, State1).
  +
  +data_update_row([undefined,Fields], #state{tableId=TableId,rawBot=RawBot,dirtyTop=DT0,dirtyCnt=DC0}=State0) ->
  +    Id = RawBot+1,          %% ToDo: map Fields to complete rows ("" for undefined fields)
       ets:insert(TableId, list_to_tuple([Id,ins,{}|Fields])),    
  -    State0#state{dirtyTop=int_min(DT0,Id),dirtyBot=Id,dirtyCount=DC0+1};
  -update_data_row([Id,Op,Fields], #state{tableId=TableId}=State0) when is_integer(Id) ->
  +    State0#state{dirtyTop=min(DT0,Id),dirtyBot=Id,dirtyCnt=DC0+1};
  +data_update_row([Id,Op,Fields], #state{tableId=TableId}=State0) when is_integer(Id) ->
       OldRow = ets:lookup(TableId, Id),
       {O,State1} = case {element(2,OldRow),Op} of
           {nop,nop} ->    {nop,State0};
  -        {nop,_} ->      DT = int_min(State0#state.dirtyTop,Id),
  -                        DB = int_max(State0#state.dirtyBot,Id),
  -                        DC = State0#state.dirtyCount+1,
  -                        {Op, State0#state{dirtyTop=DT,dirtyBot=DB,dirtyCount=DC}};
  -        {_,nop} ->      DC = State0#state.dirtyCount-1,
  -                        {nop,State0#state{dirtyCount=DC}};
  +        {nop,_} ->      DT = min(State0#state.dirtyTop,Id),
  +                        DB = max(State0#state.dirtyBot,Id),
  +                        DC = State0#state.dirtyCnt+1,
  +                        {Op, State0#state{dirtyTop=DT,dirtyBot=DB,dirtyCnt=DC}};
  +        {_,nop} ->      DC = State0#state.dirtyCnt-1,
  +                        {nop,State0#state{dirtyCnt=DC}};
           {ins,upd} ->    {ins,State0};
           {ins,ins} ->    {ins,State0};
  -        {ins,del} ->    DC = State0#state.dirtyCount-1,
  -                        {nop,State0#state{dirtyCount=DC}};
  +        {ins,del} ->    DC = State0#state.dirtyCnt-1,
  +                        {nop,State0#state{dirtyCnt=DC}};
           {del,del} ->    {del,State0};
           {del,upd} ->    {upd,State0};
           {upd,upd} ->    {upd,State0};        
  
positive.code: process_responses(_Fsm,[]) -> ok;
  process_responses(Fsm,[R|Responses]) ->
      case R of 
        #gres{loop=undefined} ->  ok;
        #gres{loop='>|...'} ->    ok;
        #gres{loop=Loop} ->
            timer:sleep(30),       
  
negative.code:     ets:insert(TableId, list_to_tuple([Id,O,element(2,OldRow)|Fields])),
      State1.
  
  
  
  %% TESTS ------------------------------------------------------------------
  
  -include_lib("eunit/include/eunit.hrl").
  
  -define(button(__Button), gui_req(button, __Button, self())).
  
  setup() -> 
      ?imem_test_setup().
  