Detailed changes
  
  
    
    @@ -6,8 +6,8 @@ import (
 )
 
 type Comment struct {
-	Author  Person `json:"author"`
-	Message string `json:"message"`
+	Author  Person
+	Message string
 
 	// Creation time of the comment.
 	// Should be used only for human display, never for ordering as we can't rely on it in a distributed system.
  
  
  
    
    @@ -23,6 +23,8 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
 			UnixTime: op.UnixTime,
 		},
 	}
+	snapshot.Author = op.Author
+	snapshot.CreatedAt = op.Time()
 	return snapshot
 }
 
  
  
  
    
    @@ -23,6 +23,8 @@ func TestCreate(t *testing.T) {
 		Comments: []bug.Comment{
 			{Author: rene, Message: "message", UnixTime: create.UnixTime},
 		},
+		Author:    rene,
+		CreatedAt: create.Time(),
 	}
 
 	if !reflect.DeepEqual(snapshot, expected) {
  
  
  
    
    @@ -6,8 +6,8 @@ import (
 )
 
 type Person struct {
-	Name  string `json:"name"`
-	Email string `json:"email"`
+	Name  string
+	Email string
 }
 
 // GetUser will query the repository for user detail and build the corresponding Person
  
  
  
    
    @@ -10,10 +10,12 @@ import (
 type Snapshot struct {
 	id string
 
-	Status   Status    `json:"status"`
-	Title    string    `json:"title"`
-	Comments []Comment `json:"comments"`
-	Labels   []Label   `json:"labels"`
+	Status    Status
+	Title     string
+	Comments  []Comment
+	Labels    []Label
+	Author    Person
+	CreatedAt time.Time
 
 	Operations []Operation
 }
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -1,4 +1,4 @@
-.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" 
+.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" 
 .nh
 .ad l
 
  
  
  
    
    @@ -9,18 +9,23 @@ import (
 )
 
 const bugTableView = "bugTableView"
+const bugTableHeaderView = "bugTableHeaderView"
+const bugTableFooterView = "bugTableFooterView"
+const bugTableInstructionView = "bugTableInstructionView"
 
 type bugTable struct {
-	cache  cache.RepoCacher
-	allIds []string
-	bugs   []*bug.Snapshot
-	cursor int
+	cache        cache.RepoCacher
+	allIds       []string
+	bugs         []*bug.Snapshot
+	pageCursor   int
+	selectCursor int
 }
 
 func newBugTable(cache cache.RepoCacher) *bugTable {
 	return &bugTable{
-		cache:  cache,
-		cursor: 0,
+		cache:        cache,
+		pageCursor:   0,
+		selectCursor: 0,
 	}
 }
 
@@ -32,7 +37,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
 		return nil
 	}
 
-	v, err := g.SetView("header", -1, -1, maxX, 3)
+	v, err := g.SetView(bugTableHeaderView, -1, -1, maxX, 3)
 
 	if err != nil {
 		if err != gocui.ErrUnknownView {
@@ -56,6 +61,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
 		v.Highlight = true
 		v.SelBgColor = gocui.ColorWhite
 		v.SelFgColor = gocui.ColorBlack
+
+		// restore the cursor
+		// window is too small to set the cursor properly, ignoring the error
+		_ = v.SetCursor(0, bt.selectCursor)
 	}
 
 	_, viewHeight := v.Size()
@@ -72,7 +81,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
 	v.Clear()
 	bt.render(v, maxX)
 
-	v, err = g.SetView("footer", -1, maxY-4, maxX, maxY)
+	v, err = g.SetView(bugTableFooterView, -1, maxY-4, maxX, maxY)
 
 	if err != nil {
 		if err != gocui.ErrUnknownView {
@@ -85,7 +94,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
 	v.Clear()
 	bt.renderFooter(v, maxX)
 
-	v, err = g.SetView("instructions", -1, maxY-2, maxX, maxY)
+	v, err = g.SetView(bugTableInstructionView, -1, maxY-2, maxX, maxY)
 
 	if err != nil {
 		if err != gocui.ErrUnknownView {
@@ -99,12 +108,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
 	}
 
 	_, err = g.SetCurrentView(bugTableView)
-
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 func (bt *bugTable) keybindings(g *gocui.Gui) error {
@@ -165,6 +169,28 @@ func (bt *bugTable) keybindings(g *gocui.Gui) error {
 		return err
 	}
 
+	// Open bug
+	if err := g.SetKeybinding(bugTableView, gocui.KeyEnter, gocui.ModNone,
+		bt.openBug); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (bt *bugTable) disable(g *gocui.Gui) error {
+	if err := g.DeleteView(bugTableView); err != nil {
+		return err
+	}
+	if err := g.DeleteView(bugTableHeaderView); err != nil {
+		return err
+	}
+	if err := g.DeleteView(bugTableFooterView); err != nil {
+		return err
+	}
+	if err := g.DeleteView(bugTableInstructionView); err != nil {
+		return err
+	}
 	return nil
 }
 
@@ -181,10 +207,10 @@ func (bt *bugTable) paginate(max int) error {
 
 func (bt *bugTable) doPaginate(allIds []string, max int) error {
 	// clamp the cursor
-	bt.cursor = maxInt(bt.cursor, 0)
-	bt.cursor = minInt(bt.cursor, len(allIds)-1)
+	bt.pageCursor = maxInt(bt.pageCursor, 0)
+	bt.pageCursor = minInt(bt.pageCursor, len(allIds)-1)
 
-	nb := minInt(len(allIds)-bt.cursor, max)
+	nb := minInt(len(allIds)-bt.pageCursor, max)
 
 	if nb < 0 {
 		bt.bugs = []*bug.Snapshot{}
@@ -192,7 +218,7 @@ func (bt *bugTable) doPaginate(allIds []string, max int) error {
 	}
 
 	// slice the data
-	ids := allIds[bt.cursor : bt.cursor+nb]
+	ids := allIds[bt.pageCursor : bt.pageCursor+nb]
 
 	bt.bugs = make([]*bug.Snapshot, len(ids))
 
@@ -217,7 +243,7 @@ func (bt *bugTable) getColumnWidths(maxX int) map[string]int {
 	m["id"] = 10
 	m["status"] = 8
 
-	left := maxX - m["id"] - m["status"]
+	left := maxX - 4 - m["id"] - m["status"]
 
 	m["summary"] = maxInt(30, left/3)
 	left -= m["summary"]
@@ -272,6 +298,7 @@ func (bt *bugTable) cursorDown(g *gocui.Gui, v *gocui.View) error {
 
 	// window is too small to set the cursor properly, ignoring the error
 	_ = v.SetCursor(0, y)
+	bt.selectCursor = y
 
 	return nil
 }
@@ -282,6 +309,7 @@ func (bt *bugTable) cursorUp(g *gocui.Gui, v *gocui.View) error {
 
 	// window is too small to set the cursor properly, ignoring the error
 	_ = v.SetCursor(0, y)
+	bt.selectCursor = y
 
 	return nil
 }
@@ -294,6 +322,7 @@ func (bt *bugTable) cursorClamp(v *gocui.View) error {
 
 	// window is too small to set the cursor properly, ignoring the error
 	_ = v.SetCursor(0, y)
+	bt.selectCursor = y
 
 	return nil
 }
@@ -308,11 +337,11 @@ func (bt *bugTable) nextPage(g *gocui.Gui, v *gocui.View) error {
 
 	bt.allIds = allIds
 
-	if bt.cursor+max >= len(allIds) {
+	if bt.pageCursor+max >= len(allIds) {
 		return nil
 	}
 
-	bt.cursor += max
+	bt.pageCursor += max
 
 	return bt.doPaginate(allIds, max)
 }
@@ -326,7 +355,13 @@ func (bt *bugTable) previousPage(g *gocui.Gui, v *gocui.View) error {
 
 	bt.allIds = allIds
 
-	bt.cursor = maxInt(0, bt.cursor-max)
+	bt.pageCursor = maxInt(0, bt.pageCursor-max)
 
 	return bt.doPaginate(allIds, max)
 }
+
+func (bt *bugTable) openBug(g *gocui.Gui, v *gocui.View) error {
+	_, y := v.Cursor()
+	ui.showBug.bug = bt.bugs[bt.pageCursor+y]
+	return ui.activateWindow(ui.showBug)
+}
  
  
  
    
    @@ -62,8 +62,7 @@ func (ep *errorPopup) layout(g *gocui.Gui) error {
 
 func (ep *errorPopup) close(g *gocui.Gui, v *gocui.View) error {
 	ep.err = ""
-	g.DeleteView(errorPopupView)
-	return nil
+	return g.DeleteView(errorPopupView)
 }
 
 func (ep *errorPopup) isActive() bool {
  
  
  
    
    @@ -0,0 +1,158 @@
+package termui
+
+import (
+	"fmt"
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/bug/operations"
+	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/util"
+	"github.com/jroimartin/gocui"
+)
+
+const showBugView = "showBugView"
+const showBugSidebarView = "showBugSidebarView"
+const showBugInstructionView = "showBugInstructionView"
+
+const timeLayout = "Jan _2 2006"
+
+type showBug struct {
+	cache cache.RepoCacher
+	bug   *bug.Snapshot
+}
+
+func newShowBug(cache cache.RepoCacher) *showBug {
+	return &showBug{
+		cache: cache,
+	}
+}
+
+func (sb *showBug) layout(g *gocui.Gui) error {
+	maxX, maxY := g.Size()
+
+	v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2)
+
+	if err != nil {
+		if err != gocui.ErrUnknownView {
+			return err
+		}
+
+		v.Frame = false
+	}
+
+	v.Clear()
+	sb.renderMain(v)
+
+	v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2)
+
+	if err != nil {
+		if err != gocui.ErrUnknownView {
+			return err
+		}
+
+		v.Frame = false
+	}
+
+	v.Clear()
+	sb.renderSidebar(v)
+
+	v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY)
+
+	if err != nil {
+		if err != gocui.ErrUnknownView {
+			return err
+		}
+
+		v.Frame = false
+		v.BgColor = gocui.ColorBlue
+
+		fmt.Fprintf(v, "[q] Return")
+	}
+
+	_, err = g.SetCurrentView(showBugView)
+	return err
+}
+
+func (sb *showBug) keybindings(g *gocui.Gui) error {
+	// Return
+	if err := g.SetKeybinding(showBugView, 'q', gocui.ModNone, sb.back); err != nil {
+		return err
+	}
+
+	if err := g.SetKeybinding(showBugView, gocui.KeyPgup, gocui.ModNone,
+		sb.scrollUp); err != nil {
+		return err
+	}
+	if err := g.SetKeybinding(showBugView, gocui.KeyPgdn, gocui.ModNone,
+		sb.scrollDown); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (sb *showBug) disable(g *gocui.Gui) error {
+	if err := g.DeleteView(showBugView); err != nil {
+		return err
+	}
+	if err := g.DeleteView(showBugSidebarView); err != nil {
+		return err
+	}
+	if err := g.DeleteView(showBugInstructionView); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (sb *showBug) renderMain(v *gocui.View) {
+	maxX, _ := v.Size()
+
+	header1 := fmt.Sprintf("[%s] %s", sb.bug.HumanId(), sb.bug.Title)
+	fmt.Fprintf(v, util.LeftPaddedString(header1, maxX, 2)+"\n\n")
+
+	header2 := fmt.Sprintf("[%s] %s opened this bug on %s",
+		sb.bug.Status, sb.bug.Author.Name, sb.bug.CreatedAt.Format(timeLayout))
+	fmt.Fprintf(v, util.LeftPaddedString(header2, maxX, 2)+"\n\n")
+
+	for _, op := range sb.bug.Operations {
+		switch op.(type) {
+
+		case operations.CreateOperation:
+			create := op.(operations.CreateOperation)
+			fmt.Fprintf(v, util.LeftPaddedString(create.Message, maxX, 6)+"\n\n\n")
+
+		case operations.AddCommentOperation:
+			comment := op.(operations.AddCommentOperation)
+			header := fmt.Sprintf("%s commented on %s",
+				comment.Author.Name, comment.Time().Format(timeLayout))
+			fmt.Fprintf(v, util.LeftPaddedString(header, maxX, 6)+"\n\n")
+			fmt.Fprintf(v, util.LeftPaddedString(comment.Message, maxX, 6)+"\n\n\n")
+		}
+	}
+
+}
+
+func (sb *showBug) renderSidebar(v *gocui.View) {
+	maxX, _ := v.Size()
+
+	title := util.LeftPaddedString("LABEL", maxX, 2)
+	fmt.Fprintf(v, title+"\n\n")
+
+	for _, label := range sb.bug.Labels {
+		fmt.Fprintf(v, util.LeftPaddedString(label.String(), maxX, 2))
+		fmt.Fprintln(v)
+	}
+}
+
+func (sb *showBug) back(g *gocui.Gui, v *gocui.View) error {
+	sb.bug = nil
+	ui.activateWindow(ui.bugTable)
+	return nil
+}
+
+func (sb *showBug) scrollUp(g *gocui.Gui, v *gocui.View) error {
+	return nil
+}
+
+func (sb *showBug) scrollDown(g *gocui.Gui, v *gocui.View) error {
+	return nil
+}
  
  
  
    
    @@ -11,20 +11,33 @@ import (
 var errTerminateMainloop = errors.New("terminate gocui mainloop")
 
 type termUI struct {
-	g            *gocui.Gui
-	gError       chan error
-	cache        cache.RepoCacher
+	g      *gocui.Gui
+	gError chan error
+	cache  cache.RepoCacher
+
 	activeWindow window
 
 	bugTable   *bugTable
+	showBug    *showBug
 	errorPopup *errorPopup
 }
 
+func (tui *termUI) activateWindow(window window) error {
+	if err := tui.activeWindow.disable(tui.g); err != nil {
+		return err
+	}
+
+	tui.activeWindow = window
+
+	return nil
+}
+
 var ui *termUI
 
 type window interface {
 	keybindings(g *gocui.Gui) error
 	layout(g *gocui.Gui) error
+	disable(g *gocui.Gui) error
 }
 
 func Run(repo repository.Repo) error {
@@ -34,6 +47,7 @@ func Run(repo repository.Repo) error {
 		gError:     make(chan error, 1),
 		cache:      c,
 		bugTable:   newBugTable(c),
+		showBug:    newShowBug(c),
 		errorPopup: newErrorPopup(),
 	}
 
@@ -107,6 +121,10 @@ func keybindings(g *gocui.Gui) error {
 		return err
 	}
 
+	if err := ui.showBug.keybindings(g); err != nil {
+		return err
+	}
+
 	if err := ui.errorPopup.keybindings(g); err != nil {
 		return err
 	}