[perf] More consolidation on things that are commit numbers.

Change-Id: I7d71dcc77a1d4ebe7cb766ca8b3cc0b2b7e9efd5
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/302117
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/go/cid/cid.go b/perf/go/cid/cid.go
index e7371c4..9035386 100644
--- a/perf/go/cid/cid.go
+++ b/perf/go/cid/cid.go
@@ -16,12 +16,11 @@
 	"go.skia.org/infra/perf/go/types"
 )
 
-// CommitID represents the time of a particular commit, where a commit could either be
-// a real commit into the repo, or an event like running a trybot.
+// CommitID represents a single commit.
 //
 // TODO(jcgregorio) Collapse this into just types.CommitNumber.
 type CommitID struct {
-	Offset int `json:"offset"` // The index number of the commit from beginning of time, or the index of the patch number in Reitveld.
+	Offset types.CommitNumber `json:"offset"`
 }
 
 // ID returns a unique ID for the CommitID.
@@ -33,9 +32,9 @@
 //
 // This is a transitional step on the way to completely replacing all CommitID
 // usage into types.CommitNumber.
-func CommitIDFromCommitNumber(n types.CommitNumber) *CommitID {
+func CommitIDFromCommitNumber(commitNumber types.CommitNumber) *CommitID {
 	return &CommitID{
-		Offset: int(n),
+		Offset: commitNumber,
 	}
 }
 
@@ -53,18 +52,23 @@
 		return nil, fmt.Errorf("Invalid ID format: %s", s)
 	}
 	return &CommitID{
-		Offset: int(i),
+		Offset: types.CommitNumber(i),
 	}, nil
 }
 
-// CommitDetail describes a CommitID.
+// CommitDetail describes a CommitNumber.
 type CommitDetail struct {
-	CommitID
-	Author    string `json:"author"`
-	Message   string `json:"message"`
-	URL       string `json:"url"`
-	Hash      string `json:"hash"`
-	Timestamp int64  `json:"ts"`
+	Offset    types.CommitNumber `json:"offset"`
+	Author    string             `json:"author"`
+	Message   string             `json:"message"`
+	URL       string             `json:"url"`
+	Hash      string             `json:"hash"`
+	Timestamp int64              `json:"ts"`
+}
+
+// ID returns a unique ID for the CommitID.
+func (c CommitDetail) ID() string {
+	return fmt.Sprintf("%s-%06d", "master", c.Offset)
 }
 
 // CommitIDLookup allows getting CommitDetails from CommitIDs.
@@ -114,7 +118,7 @@
 		}
 
 		ret[i] = &CommitDetail{
-			CommitID:  *cid,
+			Offset:    cid.Offset,
 			Author:    commit.Author,
 			Message:   fmt.Sprintf("%.7s - %s - %.50s", commit.GitHash, human.Duration(now.Sub(time.Unix(commit.Timestamp, 0))), commit.Subject),
 			URL:       urlFromParts(c.instanceConfig.GitRepoConfig.URL, commit.GitHash, commit.Subject, c.instanceConfig.GitRepoConfig.DebouceCommitURL),
diff --git a/perf/go/cid/cid_test.go b/perf/go/cid/cid_test.go
index 7d442df..0f4f862 100644
--- a/perf/go/cid/cid_test.go
+++ b/perf/go/cid/cid_test.go
@@ -20,6 +20,14 @@
 	assert.Equal(t, "master-000051", c.ID())
 }
 
+func TestCommitDetailID_Success(t *testing.T) {
+	unittest.SmallTest(t)
+	c := &CommitDetail{
+		Offset: 51,
+	}
+	assert.Equal(t, "master-000051", c.ID())
+}
+
 func TestFromID(t *testing.T) {
 	unittest.SmallTest(t)
 	testCases := []struct {
@@ -142,7 +150,7 @@
 	details[0].Message = ""
 	details[0].URL = ""
 	assert.Equal(t, &CommitDetail{
-		CommitID:  CommitID{Offset: 0},
+		Offset:    0,
 		Author:    "test <test@google.com>",
 		Message:   "",
 		URL:       "",
diff --git a/perf/go/dataframe/dataframe.go b/perf/go/dataframe/dataframe.go
index be6fbc5..4727915 100644
--- a/perf/go/dataframe/dataframe.go
+++ b/perf/go/dataframe/dataframe.go
@@ -58,8 +58,8 @@
 
 // ColumnHeader describes each column in a DataFrame.
 type ColumnHeader struct {
-	Offset    int64 `json:"offset"`
-	Timestamp int64 `json:"timestamp"` // In seconds from the Unix epoch.
+	Offset    types.CommitNumber `json:"offset"`
+	Timestamp int64              `json:"timestamp"` // In seconds from the Unix epoch.
 }
 
 // DataFrame stores Perf measurements in a table where each row is a Trace
@@ -243,7 +243,7 @@
 	commitNumbers := make([]types.CommitNumber, len(commits), len(commits))
 	for i, commit := range commits {
 		colHeader[i] = &ColumnHeader{
-			Offset:    int64(commit.CommitNumber),
+			Offset:    commit.CommitNumber,
 			Timestamp: commit.Timestamp,
 		}
 		commitNumbers[i] = commit.CommitNumber
diff --git a/perf/go/dfbuilder/dfbuilder.go b/perf/go/dfbuilder/dfbuilder.go
index 7d4a8d2..0838917 100644
--- a/perf/go/dfbuilder/dfbuilder.go
+++ b/perf/go/dfbuilder/dfbuilder.go
@@ -76,7 +76,7 @@
 	commitNumbers := make([]types.CommitNumber, len(commits), len(commits))
 	for i, commit := range commits {
 		colHeader[i] = &dataframe.ColumnHeader{
-			Offset:    int64(commit.CommitNumber),
+			Offset:    commit.CommitNumber,
 			Timestamp: commit.Timestamp,
 		}
 		commitNumbers[i] = commit.CommitNumber
@@ -259,7 +259,7 @@
 	indices := []types.CommitNumber{}
 	for _, d := range details {
 		colHeaders = append(colHeaders, &dataframe.ColumnHeader{
-			Offset:    int64(d.Offset),
+			Offset:    d.Offset,
 			Timestamp: d.Timestamp,
 		})
 		indices = append(indices, types.CommitNumber(d.Offset))
diff --git a/perf/go/dfbuilder/dfbuilder_test.go b/perf/go/dfbuilder/dfbuilder_test.go
index 39dfd09..b568239 100644
--- a/perf/go/dfbuilder/dfbuilder_test.go
+++ b/perf/go/dfbuilder/dfbuilder_test.go
@@ -122,9 +122,9 @@
 	assert.NoError(t, err)
 	assert.Len(t, df.TraceSet, 2)
 	assert.Len(t, df.Header, 3)
-	assert.Equal(t, df.Header[0].Offset, int64(0))
-	assert.Equal(t, df.Header[1].Offset, int64(1))
-	assert.Equal(t, df.Header[2].Offset, int64(7))
+	assert.Equal(t, df.Header[0].Offset, types.CommitNumber(0))
+	assert.Equal(t, df.Header[1].Offset, types.CommitNumber(1))
+	assert.Equal(t, df.Header[2].Offset, types.CommitNumber(7))
 	assert.Equal(t, df.TraceSet[",arch=x86,config=8888,"][0], float32(1.2))
 	assert.Equal(t, df.TraceSet[",arch=x86,config=8888,"][1], float32(1.3))
 	assert.Equal(t, df.TraceSet[",arch=x86,config=8888,"][2], float32(1.0))
@@ -133,7 +133,7 @@
 	assert.NoError(t, err)
 	assert.Len(t, df.TraceSet, 2)
 	assert.Len(t, df.Header, 2)
-	assert.Equal(t, df.Header[1].Offset, int64(7))
+	assert.Equal(t, df.Header[1].Offset, types.CommitNumber(7))
 	assert.Equal(t, df.TraceSet[",arch=x86,config=8888,"][1], float32(1.0))
 
 	// NewFromQueryAndRange where query doesn't encode.
diff --git a/perf/go/dryrun/dryrun.go b/perf/go/dryrun/dryrun.go
index 2034c85..0d96a92 100644
--- a/perf/go/dryrun/dryrun.go
+++ b/perf/go/dryrun/dryrun.go
@@ -59,9 +59,9 @@
 type Running struct {
 	mutex        sync.Mutex
 	whenFinished time.Time
-	Finished     bool                              `json:"finished"`    // True if the dry run is complete.
-	Message      string                            `json:"message"`     // Human readable string describing the dry run state.
-	Regressions  map[string]*regression.Regression `json:"regressions"` // All the regressions found so far.
+	Finished     bool                                          `json:"finished"`    // True if the dry run is complete.
+	Message      string                                        `json:"message"`     // Human readable string describing the dry run state.
+	Regressions  map[types.CommitNumber]*regression.Regression `json:"regressions"` // All the regressions found so far.
 }
 
 // Requests handles HTTP request for doing dryruns.
@@ -144,7 +144,7 @@
 		running := &Running{
 			Finished:    false,
 			Message:     "Starting dry run.",
-			Regressions: map[string]*regression.Regression{},
+			Regressions: map[types.CommitNumber]*regression.Regression{},
 		}
 		d.inFlight[id] = running
 		go func() {
@@ -161,16 +161,15 @@
 						sklog.Errorf("Failed to convert to Regression: %s", err)
 						return
 					}
-					id := c.ID()
-					running.Message = fmt.Sprintf("Step: %d/%d\nQuery: %q\nLooking for regressions in query results.\n  Commit: %d\n  Details: %q", queryRequest.Step+1, queryRequest.TotalQueries, queryRequest.Query, c.CommitID.Offset, message)
+					running.Message = fmt.Sprintf("Step: %d/%d\nQuery: %q\nLooking for regressions in query results.\n  Commit: %d\n  Details: %q", queryRequest.Step+1, queryRequest.TotalQueries, queryRequest.Query, c.Offset, message)
 					// We might not have found any regressions.
 					if reg.Low == nil && reg.High == nil {
 						continue
 					}
-					if origReg, ok := running.Regressions[id]; !ok {
-						running.Regressions[id] = reg
+					if origReg, ok := running.Regressions[c.Offset]; !ok {
+						running.Regressions[c.Offset] = reg
 					} else {
-						running.Regressions[id] = origReg.Merge(reg)
+						running.Regressions[c.Offset] = origReg.Merge(reg)
 					}
 				}
 			}
@@ -242,20 +241,17 @@
 	if running.Finished {
 		running.mutex.Lock()
 		defer running.mutex.Unlock()
-		keys := []string{}
+		keys := []types.CommitNumber{}
 		for id := range running.Regressions {
 			keys = append(keys, id)
 		}
-		sort.Strings(keys)
+		sort.Sort(types.CommitNumberSlice(keys))
 
 		cids := []*cid.CommitID{}
 		for _, key := range keys {
-			commitId, err := cid.FromID(key)
-			if err != nil {
-				httputils.ReportError(w, err, "Failed to parse commit id.", http.StatusInternalServerError)
-				return
-			}
-			cids = append(cids, commitId)
+			cids = append(cids, &cid.CommitID{
+				Offset: key,
+			})
 		}
 
 		cidd, err := d.cidl.Lookup(r.Context(), cids)
@@ -266,7 +262,7 @@
 		for _, details := range cidd {
 			status.Regressions = append(status.Regressions, &RegressionRow{
 				CID:        details,
-				Regression: running.Regressions[details.ID()],
+				Regression: running.Regressions[details.Offset],
 			})
 		}
 	}
diff --git a/perf/go/frontend/frontend.go b/perf/go/frontend/frontend.go
index 30afe4e..4cad8e0 100644
--- a/perf/go/frontend/frontend.go
+++ b/perf/go/frontend/frontend.go
@@ -461,9 +461,9 @@
 // cid.CommitIDs that include the range between [begin, end) and include the
 // explicit CommitID of "Source, Offset".
 type RangeRequest struct {
-	Offset int   `json:"offset"`
-	Begin  int64 `json:"begin"`
-	End    int64 `json:"end"`
+	Offset types.CommitNumber `json:"offset"`
+	Begin  int64              `json:"begin"`
+	End    int64              `json:"end"`
 }
 
 // cidRangeHandler accepts a POST'd JSON serialized RangeRequest
@@ -498,13 +498,13 @@
 	cids := []*cid.CommitID{}
 	for _, h := range df.Header {
 		cids = append(cids, &cid.CommitID{
-			Offset: int(h.Offset),
+			Offset: h.Offset,
 		})
-		if int(h.Offset) == rr.Offset {
+		if h.Offset == rr.Offset {
 			found = true
 		}
 	}
-	if !found && rr.Offset != -1 {
+	if !found && rr.Offset != types.BadCommitNumber {
 		cids = append(cids, &cid.CommitID{
 			Offset: rr.Offset,
 		})
@@ -835,10 +835,10 @@
 	}
 	details, err := f.cidl.Lookup(ctx, []*cid.CommitID{
 		{
-			Offset: begin,
+			Offset: types.CommitNumber(begin),
 		},
 		{
-			Offset: end,
+			Offset: types.CommitNumber(end),
 		},
 	})
 	if err != nil {
@@ -1122,7 +1122,7 @@
 		ids = make([]*cid.CommitID, len(commits), len(commits))
 		for i, c := range commits {
 			ids[i] = &cid.CommitID{
-				Offset: int(c.CommitNumber),
+				Offset: c.CommitNumber,
 			}
 		}
 	} else {
@@ -1138,7 +1138,7 @@
 		})
 		for _, key := range keys {
 			c := &cid.CommitID{
-				Offset: int(key),
+				Offset: key,
 			}
 			if err != nil {
 				httputils.ReportError(w, err, "Got an invalid commit id.", http.StatusInternalServerError)
diff --git a/perf/go/regression/continuous.go b/perf/go/regression/continuous.go
index a3ea950..73c0e41 100644
--- a/perf/go/regression/continuous.go
+++ b/perf/go/regression/continuous.go
@@ -134,7 +134,7 @@
 		midOffset := resp.Frame.DataFrame.Header[midPoint].Offset
 
 		id := &cid.CommitID{
-			Offset: int(midOffset),
+			Offset: midOffset,
 		}
 
 		details, err := c.cidl.Lookup(ctx, []*cid.CommitID{id})
diff --git a/perf/go/regression/dfiter_test.go b/perf/go/regression/dfiter_test.go
index 3bc4914..adefa8a 100644
--- a/perf/go/regression/dfiter_test.go
+++ b/perf/go/regression/dfiter_test.go
@@ -202,9 +202,9 @@
 	df, err := iter.Value(ctx)
 	require.NoError(t, err)
 	assert.Equal(t, 3, len(df.Header))
-	assert.Equal(t, int64(0), df.Header[0].Offset)
-	assert.Equal(t, int64(1), df.Header[1].Offset)
-	assert.Equal(t, int64(7), df.Header[2].Offset)
+	assert.Equal(t, types.CommitNumber(0), df.Header[0].Offset)
+	assert.Equal(t, types.CommitNumber(1), df.Header[1].Offset)
+	assert.Equal(t, types.CommitNumber(7), df.Header[2].Offset)
 }
 
 func TestNewDataFrameIterator_ExactDataframeRequest_ErrIfWeSearchBeforeFirstCommit(t *testing.T) {
diff --git a/perf/go/regression/dsregressionstore/dsregressionstore.go b/perf/go/regression/dsregressionstore/dsregressionstore.go
index 0786d41..f959af2 100644
--- a/perf/go/regression/dsregressionstore/dsregressionstore.go
+++ b/perf/go/regression/dsregressionstore/dsregressionstore.go
@@ -78,11 +78,11 @@
 
 // Range implements the RegressionStore interface.
 func (s *RegressionStoreDS) Range(ctx context.Context, begin, end types.CommitNumber) (map[types.CommitNumber]*regression.AllRegressionsForCommit, error) {
-	beginDetail, err := s.lookup(ctx, &cid.CommitID{Offset: int(begin)})
+	beginDetail, err := s.lookup(ctx, &cid.CommitID{Offset: begin})
 	if err != nil {
 		return nil, skerr.Wrapf(err, "Failed for begin=%d", begin)
 	}
-	endDetail, err := s.lookup(ctx, &cid.CommitID{Offset: int(end)})
+	endDetail, err := s.lookup(ctx, &cid.CommitID{Offset: end})
 	if err != nil {
 		return nil, skerr.Wrapf(err, "Failed for end=%d", end)
 	}
diff --git a/perf/go/regression/fromsummary.go b/perf/go/regression/fromsummary.go
index a4d2a0c..c793de4 100644
--- a/perf/go/regression/fromsummary.go
+++ b/perf/go/regression/fromsummary.go
@@ -20,7 +20,7 @@
 	midOffset := resp.Frame.DataFrame.Header[midPoint].Offset
 
 	id := &cid.CommitID{
-		Offset: int(midOffset),
+		Offset: midOffset,
 	}
 
 	details, err := cidl.Lookup(ctx, []*cid.CommitID{id})
diff --git a/perf/go/regression/regressiontest/regressiontest.go b/perf/go/regression/regressiontest/regressiontest.go
index e813095..25cb74f 100644
--- a/perf/go/regression/regressiontest/regressiontest.go
+++ b/perf/go/regression/regressiontest/regressiontest.go
@@ -32,9 +32,7 @@
 	lookupValues := []*cid.CommitDetail{}
 	for i := 0; i < len(timestamps); i++ {
 		lookupValues = append(lookupValues, &cid.CommitDetail{
-			CommitID: cid.CommitID{
-				Offset: i,
-			},
+			Offset:    types.CommitNumber(i),
 			Timestamp: timestamps[i],
 		})
 	}
@@ -49,9 +47,7 @@
 func getTestVars() (context.Context, *cid.CommitDetail) {
 	ctx := context.Background()
 	c := &cid.CommitDetail{
-		CommitID: cid.CommitID{
-			Offset: 1,
-		},
+		Offset:    1,
 		Timestamp: timestamps[1],
 	}
 
diff --git a/perf/go/types/types.go b/perf/go/types/types.go
index 39d4bca..7052022 100644
--- a/perf/go/types/types.go
+++ b/perf/go/types/types.go
@@ -15,6 +15,13 @@
 // BadCommitNumber is an invalid CommitNumber.
 const BadCommitNumber CommitNumber = -1
 
+// CommitNumberSlice is a utility class for sorting CommitNumbers.
+type CommitNumberSlice []CommitNumber
+
+func (p CommitNumberSlice) Len() int           { return len(p) }
+func (p CommitNumberSlice) Less(i, j int) bool { return p[i] < p[j] }
+func (p CommitNumberSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
 // TileNumber is the number of a Tile in the TraceStore. The first tile is
 // always 0. The number of commits per Tile is configured per TraceStore.
 type TileNumber int32
diff --git a/perf/go/types/types_test.go b/perf/go/types/types_test.go
index 1161431..5ede11b 100644
--- a/perf/go/types/types_test.go
+++ b/perf/go/types/types_test.go
@@ -1,6 +1,7 @@
 package types
 
 import (
+	"sort"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -36,3 +37,10 @@
 	assert.Equal(t, CommitNumber(256), begin)
 	assert.Equal(t, CommitNumber(511), end)
 }
+
+func TestCommitNumberSlice_Sort_Success(t *testing.T) {
+	unittest.SmallTest(t)
+	toSort := CommitNumberSlice{2, BadCommitNumber, 1}
+	sort.Sort(toSort)
+	assert.Equal(t, CommitNumberSlice{BadCommitNumber, 1, 2}, toSort)
+}
diff --git a/perf/modules/commit-detail-panel-sk/commit-detail-panel-sk-demo.ts b/perf/modules/commit-detail-panel-sk/commit-detail-panel-sk-demo.ts
index a74cbd5..61977c5 100644
--- a/perf/modules/commit-detail-panel-sk/commit-detail-panel-sk-demo.ts
+++ b/perf/modules/commit-detail-panel-sk/commit-detail-panel-sk-demo.ts
@@ -8,9 +8,7 @@
     url: 'skia.googlesource.com/bar',
     message: 'Commit from foo.',
     hash: 'abcdef123',
-    CommitID: {
-      offset: 1,
-    },
+    offset: 1,
   },
   {
     ts: 1439648914,
@@ -18,9 +16,7 @@
     url: 'skia.googlesource.com/foo',
     message: 'Commit from bar',
     hash: 'abcdef456',
-    CommitID: {
-      offset: 2,
-    },
+    offset: 2,
   },
   {
     ts: 1439649951,
@@ -28,9 +24,7 @@
     url: 'https://codereview.chromium.org/1490543002',
     message: 'Whitespace change',
     hash: 'abcdef789',
-    CommitID: {
-      offset: 3,
-    },
+    offset: 3,
   },
   {
     ts: 1439699951,
@@ -38,9 +32,7 @@
     url: 'https://codereview.chromium.org/1490543002',
     message: 'Another whitespace change',
     hash: 'abcdef101112',
-    CommitID: {
-      offset: 4,
-    },
+    offset: 4,
   },
 ];
 
@@ -50,7 +42,7 @@
   .querySelectorAll<CommitDetailPanelSk>('commit-detail-panel-sk')
   .forEach((panel) => {
     panel.details = commitinfo;
-    panel.addEventListener('commit-selected', function (e: Event) {
+    panel.addEventListener('commit-selected', (e: Event) => {
       evt.textContent = JSON.stringify((e as CustomEvent).detail, null, '  ');
     });
   });
diff --git a/perf/modules/commit-detail-picker-sk/commit-detail-picker-sk-demo.ts b/perf/modules/commit-detail-picker-sk/commit-detail-picker-sk-demo.ts
index 6023326..285feee 100644
--- a/perf/modules/commit-detail-picker-sk/commit-detail-picker-sk-demo.ts
+++ b/perf/modules/commit-detail-picker-sk/commit-detail-picker-sk-demo.ts
@@ -8,9 +8,7 @@
     url: 'skia.googlesource.com/bar',
     message: 'Commit from foo.',
     hash: 'abcdef123',
-    CommitID: {
-      offset: 1,
-    },
+    offset: 1,
   },
   {
     ts: 1439648914,
@@ -18,9 +16,7 @@
     url: 'skia.googlesource.com/foo',
     message: 'Commit from bar',
     hash: 'abcdef456',
-    CommitID: {
-      offset: 2,
-    },
+    offset: 2,
   },
   {
     ts: 1439649951,
@@ -28,9 +24,7 @@
     url: 'https://codereview.chromium.org/1490543002',
     message: 'Whitespace change',
     hash: 'abcdef789',
-    CommitID: {
-      offset: 3,
-    },
+    offset: 3,
   },
   {
     ts: 1439699951,
@@ -38,9 +32,7 @@
     url: 'https://codereview.chromium.org/1490543002',
     message: 'Another whitespace change',
     hash: 'abcdef101112',
-    CommitID: {
-      offset: 4,
-    },
+    offset: 4,
   },
 ];
 
@@ -50,7 +42,7 @@
   .querySelectorAll<CommitDetailPickerSk>('commit-detail-picker-sk')
   .forEach((panel) => {
     panel.details = commitinfo;
-    panel.addEventListener('commit-selected', function (e: Event) {
+    panel.addEventListener('commit-selected', (e: Event) => {
       evt.textContent = JSON.stringify((e as CustomEvent).detail, null, '  ');
     });
   });
diff --git a/perf/modules/commit-detail-sk/commit-detail-sk-demo.ts b/perf/modules/commit-detail-sk/commit-detail-sk-demo.ts
index 9ef101a..b00a058 100644
--- a/perf/modules/commit-detail-sk/commit-detail-sk-demo.ts
+++ b/perf/modules/commit-detail-sk/commit-detail-sk-demo.ts
@@ -10,9 +10,7 @@
       'e699a3a - 19h 59m - Roll third_party/externals/swiftshader 522d5121905',
     author: '',
     ts: 0,
-    CommitID: {
-      offset: 0,
-    },
+    offset: 0,
   };
   ele.querySelector('div')!.click();
 });
diff --git a/perf/modules/commit-detail-sk/commit-detail-sk.ts b/perf/modules/commit-detail-sk/commit-detail-sk.ts
index 5373b74..7435857 100644
--- a/perf/modules/commit-detail-sk/commit-detail-sk.ts
+++ b/perf/modules/commit-detail-sk/commit-detail-sk.ts
@@ -13,10 +13,8 @@
 import { CommitDetail } from '../json';
 
 export class CommitDetailSk extends ElementSk {
-  private static template = (ele: CommitDetailSk) => html`<div
-      @click=${() => ele._click()}
-      class="linkish"
-    >
+  private static template = (ele: CommitDetailSk) => html`
+    <div @click=${() => ele._click()} class="linkish">
       <pre>${ele.cid.message}</pre>
     </div>
     <div class="tip hidden">
@@ -24,7 +22,8 @@
       <a href="/g/c/${ele.cid.hash}">Cluster</a>
       <a href="/g/t/${ele.cid.hash}">Triage</a>
       <a href="${ele.cid.url}">Commit</a>
-    </div>`;
+    </div>
+  `;
 
   private _cid: CommitDetail;
 
@@ -36,9 +35,7 @@
       url: '',
       ts: 0,
       hash: '',
-      CommitID: {
-        offset: 0,
-      },
+      offset: 0,
     };
   }
 
diff --git a/perf/modules/json/index.ts b/perf/modules/json/index.ts
index b879031..ed14262 100644
--- a/perf/modules/json/index.ts
+++ b/perf/modules/json/index.ts
@@ -21,12 +21,8 @@
 	category: string;
 }
 
-export interface CommitID {
-	offset: number;
-}
-
 export interface CommitDetail {
-	CommitID: CommitID;
+	offset: CommitNumber;
 	author: string;
 	message: string;
 	url: string;
@@ -48,7 +44,7 @@
 }
 
 export interface ColumnHeader {
-	offset: number;
+	offset: CommitNumber;
 	timestamp: number;
 }
 
@@ -147,6 +143,10 @@
 	value: RegressionDetectionResponse | null;
 }
 
+export interface CommitID {
+	offset: CommitNumber;
+}
+
 export interface CommitDetailsRequest {
 	cid: CommitID;
 	traceid: string;
@@ -164,7 +164,7 @@
 }
 
 export interface RangeRequest {
-	offset: number;
+	offset: CommitNumber;
 	begin: number;
 	end: number;
 }
@@ -250,6 +250,8 @@
 
 export type Direction = "UP" | "DOWN" | "BOTH";
 
+export type CommitNumber = number;
+
 export type StepFitStatus = "Low" | "High" | "Uninteresting";
 
 export type RequestType = 1 | 0;