dmitri.shuralyov.com/website/gido/...

Initial commit.
dmitshur committed 2 years ago commit 799f57b8fdf59d8e51d29b7ac6e66ddae8433ed0
LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2018 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
_data/style.css
@@ -0,0 +1,128 @@
+body {
+	font-family: Go;
+	font-size: 14px;
+	color: rgb(35, 35, 35);
+}
+a {
+	color: #4183c4;
+	text-decoration: none;
+}
+a:hover {
+	text-decoration: underline;
+}
+a.black {
+	color: black;
+}
+a.black:hover {
+	color: #4183c4;
+	text-decoration: none;
+}
+
+code {
+	font-family: "Go Mono";
+}
+kbd {
+	display: inline-block;
+	padding: 3px 5px 2px 5px;
+	font-family: "Go Mono";
+	font-size: 12px;
+	line-height: 10px;
+	background-color: #fafbfc;
+	border: solid 1px #c6cbd1;
+	border-bottom-width: 2px;
+	border-bottom-color: #959da5;
+	border-radius: 3px;
+}
+
+.table {
+	font-size: 14px;
+	border-collapse: collapse;
+	background-color: transparent;
+	width: 100%;
+	max-width: 100%;
+	margin-bottom: 20px;
+}
+.table th, .table td {
+	padding: 8px;
+	line-height: 1.42857143;
+	vertical-align: top;
+}
+.table td {
+	border-top: 1px solid #dddddd;
+}
+.table thead th {
+	text-align: left;
+	vertical-align: bottom;
+	border-bottom: 2px solid #dddddd;
+}
+.table .table {
+	background-color: #fff;
+}
+.table-sm th, .table-sm td {
+	padding: 5px;
+}
+
+.gray {
+	color: #888;
+}
+.lightgray {
+	color: #ddd;
+}
+.tiny {
+	font-size: 12px;
+}
+
+div.list-entry {
+	margin-top: 12px;
+}
+div.list-entry-container {
+	width: 100%;
+	box-sizing: border-box;
+}
+div.list-entry-border {
+	border: 1px solid #ddd;
+	border-radius: 4px;
+}
+div.list-entry-header {
+	font-size: 13px;
+	background-color: #f8f8f8;
+	padding: 10px;
+	border-radius: 4px 4px 0 0;
+	border-bottom: 1px solid #eee;
+}
+.hash-selected div.list-entry-border {
+	border: 1px solid #8ca2d9;
+}
+.hash-selected div.list-entry-header {
+	background-color: #dbe5ff;
+	border-bottom: 1px solid #8ca2d9;
+}
+div.list-entry-header.tabs {
+	padding: 16px 10px 6px 10px;
+}
+div.list-entry-header.tabs-title {
+	padding-bottom: 6px;
+	background-color: #fff;
+}
+div.list-entry-body {
+	padding: 10px;
+}
+
+div.multilist-entry:not(:nth-child(0n+2)) {
+	border: 0px solid #ddd;
+	border-top-width: 1px;
+}
+
+div.list-entry-header nav a {
+	color: #767676;
+	text-decoration: none;
+}
+div.list-entry-header nav a:hover,
+div.list-entry-header nav a:active,
+div.list-entry-header nav a:focus {
+	color: #000;
+}
+div.list-entry-header nav .selected {
+	color: #000;
+	font-weight: bold;
+}
assets/assets.go
@@ -0,0 +1,24 @@
+// +build dev
+
+package assets
+
+import (
+	"go/build"
+	"log"
+	"net/http"
+
+	"github.com/shurcooL/httpfs/union"
+)
+
+// Assets contains assets for issuesapp.
+var Assets = union.New(map[string]http.FileSystem{
+	"/assets": http.Dir(importPathToDir("dmitri.shuralyov.com/website/gido/_data")),
+})
+
+func importPathToDir(importPath string) string {
+	p, err := build.Import(importPath, "", build.FindOnly)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	return p.Dir
+}
assets/assets_vfsdata.go
@@ -0,0 +1,193 @@
+// Code generated by vfsgen; DO NOT EDIT.
+
+// +build !dev
+
+package assets
+
+import (
+	"bytes"
+	"compress/gzip"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	pathpkg "path"
+	"time"
+)
+
+// Assets contains assets for issuesapp.
+var Assets = func() http.FileSystem {
+	fs := vfsgen۰FS{
+		"/": &vfsgen۰DirInfo{
+			name:    "/",
+			modTime: time.Time{},
+		},
+		"/assets": &vfsgen۰DirInfo{
+			name:    "assets",
+			modTime: time.Date(2018, 3, 10, 2, 38, 16, 913855967, time.UTC),
+		},
+		"/assets/style.css": &vfsgen۰CompressedFileInfo{
+			name:             "style.css",
+			modTime:          time.Date(2018, 3, 12, 21, 9, 27, 876382302, time.UTC),
+			uncompressedSize: 2137,
+
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x55\xeb\x6e\xa3\x3a\x10\xfe\x1d\x9e\xc2\x6a\x75\xa4\x56\xa7\x44\x90\x5b\xa9\x79\x80\xfe\x3a\x0f\x61\x6c\x13\xac\x3a\x36\x32\x93\x94\x9c\xd5\xbe\xfb\xca\x17\x6e\x09\x61\x77\x83\x50\x80\x19\x7f\x33\xf3\x7d\x33\x76\xa1\xd9\x15\xfd\x88\x56\xa5\x56\x10\x97\xe4\x24\xe4\x15\xa3\x4f\x9d\x87\x2f\x8d\xf8\x9f\x63\x94\xee\xea\x36\x8f\x56\x54\x4b\x6d\x30\x32\xc7\xe2\x65\xbb\x7f\x43\xfe\x7e\xcd\xa3\x9f\x11\xb1\x10\xc1\xfc\xbc\x4b\xb3\x2d\xdd\xe5\xd1\x0a\x78\x0b\x31\xe3\x54\x1b\x02\x42\x2b\x8c\x94\x56\xdc\xb9\xe3\x4a\x5f\xb8\xb1\x8b\xee\x7c\xce\x8a\x71\x23\x45\x70\x5c\x17\x92\xd0\xaf\x11\xba\x7b\x1f\x99\x06\xa4\x3f\x0f\x1f\x51\xcd\xf8\x5d\xd1\x4f\x9f\x1a\xfd\xa7\x95\x7e\xb2\x2e\x5f\x05\xb3\x0e\x4c\x34\xb5\x24\x57\x8c\x84\xb2\x29\xc5\x85\xd4\x36\xfa\xaa\x26\x8c\x09\x75\xc4\x68\x5b\xb7\x68\x5f\xb7\x68\xe3\xff\xf3\xc7\x98\x13\x3e\x37\xce\xd5\x41\x56\x5c\x1c\x2b\xc0\x28\x4d\xdc\xb7\x82\xd0\xaf\xa3\xd1\x67\xc5\xe2\xae\xa0\x92\x94\x45\x49\xad\x4d\x1b\xc6\x0d\x46\x8d\x96\x82\xa1\xb4\x6e\xd1\x33\x3d\xd0\x82\xa5\xbd\x2d\x2e\x34\x80\x3e\xc5\xdf\x82\x41\x85\x91\x0f\x33\x35\x75\xa8\x1f\xfb\x0f\x46\xf6\x83\xd9\x10\x26\xce\x8d\xab\xc8\x71\xb4\x06\x52\xc8\x81\xa5\x49\x23\x84\x25\x54\x4b\x49\xea\x86\x63\xd4\x3d\xcd\x16\x00\x86\xa8\xa6\x26\x86\x2b\xc8\xa3\x55\x48\x2d\x4d\x92\x7f\xf2\x68\x75\x22\x6d\x7c\xfb\xc5\x1c\x85\x0a\xd9\x62\xb4\x49\x7c\x3e\x21\x1d\xa8\xde\x50\xf7\xe8\x14\xea\x95\xc8\x66\x28\x5d\xef\x36\xd9\xfe\x3d\xdd\x6d\xf3\x68\x75\xe1\x06\x04\x25\x32\x26\x52\x1c\x15\x46\xa0\xeb\x31\xae\x03\x0b\x75\x81\xae\xb1\xa3\xd7\x13\xfd\xcc\xdc\x6f\x92\x05\x27\x0c\x41\xd5\x37\x70\xc0\x94\xbc\x84\x99\x50\xbe\x96\x5b\x25\x9c\x3c\x0f\x43\x0c\xf4\xcf\x75\x44\x59\x0e\xae\x71\x73\x1a\xd1\xe2\xde\xa6\xcc\xec\x3b\x45\x8f\x86\x5c\xc7\x93\x92\x65\x99\x83\x91\x96\xae\x5b\x63\x9f\x8e\x50\xd7\xdb\x2e\xd8\x04\x44\x26\x2e\x6b\x29\x1a\x88\xb9\x02\xe3\xbc\x82\x7a\x9e\xc1\xe0\x36\xf5\x8a\xa9\x56\x40\x84\xf2\x43\x3b\xd5\xbe\xd0\xad\x0d\xe1\xb2\xee\xb9\x9a\xc3\xf0\xc6\x41\xb2\x5b\xb9\xee\xdb\x7a\x37\x9b\x8b\xd5\xd1\xe3\x8c\xcb\xdb\x3e\x9c\xc4\xcc\x5e\xe3\xf9\xef\xa6\xf6\x2e\x98\xbb\x13\x94\xdc\xab\x3e\xca\x94\x73\xb7\x1f\xad\x2b\xd2\x54\x71\xc3\x25\xa7\xc0\x19\xfa\x9b\x5a\x33\x4a\x36\xec\xe3\xf7\x20\x43\xa1\x33\x65\xb1\x82\xef\x6d\x47\x2d\x64\x3a\xc4\x99\x05\xb6\xbd\xd7\x4c\x9a\x2e\x3d\xd4\xad\x63\x07\x75\x0f\xcb\x8b\x63\x10\xe0\xfb\x3d\x40\xf4\x59\x1c\x1e\xaa\xe1\xa7\xe0\x8e\x2e\x7f\xa0\xdd\x48\x14\xba\xf5\x74\x96\x20\x06\x6f\xac\x34\xbc\x60\x05\x55\x4c\x2b\x21\xd9\x4b\xa2\xfe\xdd\xbc\xbe\x8e\xb9\x4e\x1e\xf4\x15\xe8\xba\xdf\xb8\x66\xa7\xa1\xa3\x5c\x91\x0b\x9a\x1c\x8f\xef\x07\x7b\x2d\x9d\x4f\x0b\x40\xfe\xb8\x7b\x5b\x74\x21\x14\xc4\x85\x2f\xfb\x94\x9a\x9e\x9b\x71\x56\x49\x92\x2c\x87\x5e\xf7\xad\x75\xbb\xca\xcf\xce\x77\xd8\x74\x0b\x2d\xdd\xc6\xf1\x2b\x00\x00\xff\xff\xda\x4e\x50\x1d\x59\x08\x00\x00"),
+		},
+	}
+	fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+		fs["/assets"].(os.FileInfo),
+	}
+	fs["/assets"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+		fs["/assets/style.css"].(os.FileInfo),
+	}
+
+	return fs
+}()
+
+type vfsgen۰FS map[string]interface{}
+
+func (fs vfsgen۰FS) Open(path string) (http.File, error) {
+	path = pathpkg.Clean("/" + path)
+	f, ok := fs[path]
+	if !ok {
+		return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
+	}
+
+	switch f := f.(type) {
+	case *vfsgen۰CompressedFileInfo:
+		gr, err := gzip.NewReader(bytes.NewReader(f.compressedContent))
+		if err != nil {
+			// This should never happen because we generate the gzip bytes such that they are always valid.
+			panic("unexpected error reading own gzip compressed bytes: " + err.Error())
+		}
+		return &vfsgen۰CompressedFile{
+			vfsgen۰CompressedFileInfo: f,
+			gr: gr,
+		}, nil
+	case *vfsgen۰DirInfo:
+		return &vfsgen۰Dir{
+			vfsgen۰DirInfo: f,
+		}, nil
+	default:
+		// This should never happen because we generate only the above types.
+		panic(fmt.Sprintf("unexpected type %T", f))
+	}
+}
+
+// vfsgen۰CompressedFileInfo is a static definition of a gzip compressed file.
+type vfsgen۰CompressedFileInfo struct {
+	name              string
+	modTime           time.Time
+	compressedContent []byte
+	uncompressedSize  int64
+}
+
+func (f *vfsgen۰CompressedFileInfo) Readdir(count int) ([]os.FileInfo, error) {
+	return nil, fmt.Errorf("cannot Readdir from file %s", f.name)
+}
+func (f *vfsgen۰CompressedFileInfo) Stat() (os.FileInfo, error) { return f, nil }
+
+func (f *vfsgen۰CompressedFileInfo) GzipBytes() []byte {
+	return f.compressedContent
+}
+
+func (f *vfsgen۰CompressedFileInfo) Name() string       { return f.name }
+func (f *vfsgen۰CompressedFileInfo) Size() int64        { return f.uncompressedSize }
+func (f *vfsgen۰CompressedFileInfo) Mode() os.FileMode  { return 0444 }
+func (f *vfsgen۰CompressedFileInfo) ModTime() time.Time { return f.modTime }
+func (f *vfsgen۰CompressedFileInfo) IsDir() bool        { return false }
+func (f *vfsgen۰CompressedFileInfo) Sys() interface{}   { return nil }
+
+// vfsgen۰CompressedFile is an opened compressedFile instance.
+type vfsgen۰CompressedFile struct {
+	*vfsgen۰CompressedFileInfo
+	gr      *gzip.Reader
+	grPos   int64 // Actual gr uncompressed position.
+	seekPos int64 // Seek uncompressed position.
+}
+
+func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
+	if f.grPos > f.seekPos {
+		// Rewind to beginning.
+		err = f.gr.Reset(bytes.NewReader(f.compressedContent))
+		if err != nil {
+			return 0, err
+		}
+		f.grPos = 0
+	}
+	if f.grPos < f.seekPos {
+		// Fast-forward.
+		_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
+		if err != nil {
+			return 0, err
+		}
+		f.grPos = f.seekPos
+	}
+	n, err = f.gr.Read(p)
+	f.grPos += int64(n)
+	f.seekPos = f.grPos
+	return n, err
+}
+func (f *vfsgen۰CompressedFile) Seek(offset int64, whence int) (int64, error) {
+	switch whence {
+	case io.SeekStart:
+		f.seekPos = 0 + offset
+	case io.SeekCurrent:
+		f.seekPos += offset
+	case io.SeekEnd:
+		f.seekPos = f.uncompressedSize + offset
+	default:
+		panic(fmt.Errorf("invalid whence value: %v", whence))
+	}
+	return f.seekPos, nil
+}
+func (f *vfsgen۰CompressedFile) Close() error {
+	return f.gr.Close()
+}
+
+// vfsgen۰DirInfo is a static definition of a directory.
+type vfsgen۰DirInfo struct {
+	name    string
+	modTime time.Time
+	entries []os.FileInfo
+}
+
+func (d *vfsgen۰DirInfo) Read([]byte) (int, error) {
+	return 0, fmt.Errorf("cannot Read from directory %s", d.name)
+}
+func (d *vfsgen۰DirInfo) Close() error               { return nil }
+func (d *vfsgen۰DirInfo) Stat() (os.FileInfo, error) { return d, nil }
+
+func (d *vfsgen۰DirInfo) Name() string       { return d.name }
+func (d *vfsgen۰DirInfo) Size() int64        { return 0 }
+func (d *vfsgen۰DirInfo) Mode() os.FileMode  { return 0755 | os.ModeDir }
+func (d *vfsgen۰DirInfo) ModTime() time.Time { return d.modTime }
+func (d *vfsgen۰DirInfo) IsDir() bool        { return true }
+func (d *vfsgen۰DirInfo) Sys() interface{}   { return nil }
+
+// vfsgen۰Dir is an opened dir instance.
+type vfsgen۰Dir struct {
+	*vfsgen۰DirInfo
+	pos int // Position within entries for Seek and Readdir.
+}
+
+func (d *vfsgen۰Dir) Seek(offset int64, whence int) (int64, error) {
+	if offset == 0 && whence == io.SeekStart {
+		d.pos = 0
+		return 0, nil
+	}
+	return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
+}
+
+func (d *vfsgen۰Dir) Readdir(count int) ([]os.FileInfo, error) {
+	if d.pos >= len(d.entries) && count > 0 {
+		return nil, io.EOF
+	}
+	if count <= 0 || count > len(d.entries)-d.pos {
+		count = len(d.entries) - d.pos
+	}
+	e := d.entries[d.pos : d.pos+count]
+	d.pos += count
+	return e, nil
+}
assets/doc.go
@@ -0,0 +1,4 @@
+//go:generate vfsgendev -source="dmitri.shuralyov.com/website/gido/assets".Assets
+
+// Package assets contains assets for gido.
+package assets
assets/external.go
@@ -0,0 +1,8 @@
+package assets
+
+import "github.com/shurcooL/gofontwoff"
+
+var (
+	// Fonts contains the Go font family in Web Open Font Format.
+	Fonts = gofontwoff.Assets
+)
main.go
@@ -0,0 +1,359 @@
+// gido is the command that powers the https://goissues.org website.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"mime"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"sort"
+	"strings"
+
+	"dmitri.shuralyov.com/website/gido/assets"
+	"github.com/shurcooL/htmlg"
+	"github.com/shurcooL/httperror"
+	"github.com/shurcooL/httpgzip"
+	"github.com/shurcooL/issues"
+	"github.com/shurcooL/issuesapp/component"
+	"golang.org/x/net/html"
+	"golang.org/x/net/html/atom"
+)
+
+var (
+	httpFlag          = flag.String("http", ":8080", "Listen for HTTP connections on this address.")
+	analyticsFileFlag = flag.String("analytics-file", "", "Optional path to file containing analytics HTML to insert at the beginning of <head>.")
+)
+
+func main() {
+	flag.Parse()
+
+	err := run()
+	if err != nil {
+		log.Fatalln(err)
+	}
+}
+
+func run() error {
+	if err := mime.AddExtensionType(".woff2", "font/woff2"); err != nil {
+		return err
+	}
+
+	var analyticsHTML []byte
+	if *analyticsFileFlag != "" {
+		var err error
+		analyticsHTML, err = ioutil.ReadFile(*analyticsFileFlag)
+		if err != nil {
+			return err
+		}
+	}
+
+	printServingAt(*httpFlag)
+	err := http.ListenAndServe(*httpFlag, top{&errorHandler{handler: (&handler{
+		analyticsHTML: analyticsHTML,
+		fontsHandler:  httpgzip.FileServer(assets.Fonts, httpgzip.FileServerOptions{ServeError: httpgzip.Detailed}),
+		assetsHandler: httpgzip.FileServer(assets.Assets, httpgzip.FileServerOptions{ServeError: httpgzip.Detailed}),
+		s:             newService(),
+	}).ServeHTTP}})
+	return err
+}
+
+func printServingAt(addr string) {
+	hostPort := addr
+	if strings.HasPrefix(hostPort, ":") {
+		hostPort = "localhost" + hostPort
+	}
+	fmt.Printf("serving at http://%s/\n", hostPort)
+}
+
+// handler handles all goissues requests. It acts like a request multiplexer,
+// choosing from various endpoints and parsing the import path from URL.
+type handler struct {
+	analyticsHTML []byte
+	fontsHandler  http.Handler
+	assetsHandler http.Handler
+	s             *service
+}
+
+func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) error {
+	// Handle "/".
+	if req.URL.Path == "/" {
+		return h.ServeIndex(w, req)
+	}
+
+	// Handle "/assets/fonts/...".
+	if req.URL.Path == "/assets/fonts" || strings.HasPrefix(req.URL.Path, "/assets/fonts/") {
+		req = stripPrefix(req, len("/assets/fonts"))
+		h.fontsHandler.ServeHTTP(w, req)
+		return nil
+	}
+
+	// Handle (the rest of) "/assets/...".
+	if req.URL.Path == "/assets" || strings.HasPrefix(req.URL.Path, "/assets/") {
+		h.assetsHandler.ServeHTTP(w, req)
+		return nil
+	}
+
+	// Handle "/..." URLs.
+	if canonicalPath := path.Clean(req.URL.Path); req.URL.Path != canonicalPath {
+		// Redirect to canonical path (no trailing slash, etc.).
+		if req.URL.RawQuery != "" {
+			canonicalPath += "?" + req.URL.RawQuery
+		}
+		http.Redirect(w, req, canonicalPath, http.StatusFound)
+		return nil
+	}
+	pkg := req.URL.Path[1:]
+	return h.ServeIssues(w, req, pkg)
+}
+
+const htmlPart1, htmlPart2, htmlPart3 = `<html>
+	<head>
+`, `		<title>Go Issues</title>
+		<meta name="viewport" content="width=device-width">
+		<link href="/assets/fonts/fonts.css" rel="stylesheet" type="text/css">
+		<link href="/assets/style.css" rel="stylesheet" type="text/css">
+	</head>
+	<body style="margin: 0; position: relative;">
+		<header style="background-color: hsl(209, 51%, 92%);">
+			<div style="max-width: 800px; margin: 0 auto 0 auto; padding: 0 15px 0 15px;">
+				<a class="black" href="/"><strong style="padding: 15px 0 15px 0; display: inline-block;">Go Issues</strong></a>
+			</div>
+		</header>
+
+		<main style="max-width: 800px; margin: 0 auto 0 auto; padding: 0 15px 120px 15px;">`, `</main>
+
+		<footer style="background-color: hsl(209, 51%, 92%); position: absolute; bottom: 0; left: 0; right: 0;">
+			<div style="max-width: 800px; margin: 0 auto 0 auto; padding: 0 15px 0 15px; text-align: right;">
+				<span style="padding: 15px 0 15px 0; display: inline-block;"><a href="https://dmitri.shuralyov.com/website/gido/...$issues">Website Issues</a></span>
+			</div>
+		</footer>
+	</body>
+</html>
+`
+
+func (h *handler) ServeIndex(w http.ResponseWriter, req *http.Request) error {
+	if req.Method != http.MethodGet {
+		return httperror.Method{Allowed: []string{http.MethodGet}}
+	}
+
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	_, err := io.WriteString(w, htmlPart1)
+	if err != nil {
+		return err
+	}
+	_, err = w.Write(h.analyticsHTML)
+	if err != nil {
+		return err
+	}
+	_, err = io.WriteString(w, htmlPart2)
+	if err != nil {
+		return err
+	}
+
+	// Write the About section.
+	_, err = io.WriteString(w, `<h3 style="margin-top: 30px;">About</h3>
+
+			<p>Go Issues shows issues for Go packages.
+			It's just like <a href="https://godoc.org">godoc.org</a>, but for issues.</p>
+
+			<p>To view issues of a Go package with a given import path, navigate to <code>goissues.org/import/path</code>
+			using your browser's address bar (<kbd>⌘Cmd</kbd>+<kbd>L</kbd> or <kbd>Ctrl</kbd>+<kbd>L</kbd>).</p>
+
+			<p>Supported import paths include:</p>
+
+			<ul>
+			<li><a href="https://golang.org/pkg/#stdlib">Standard library</a> (e.g., <code>io</code>, <code>net/http</code>, etc.),</li>
+			<li><a href="https://golang.org/pkg/#subrepo">Sub-repositories</a> (i.e., <code>golang.org/x/...</code>).</li>
+			</ul>
+
+			<p>Import paths of 3rd party packages (e.g., <code>github.com/...</code>) are not supported.</p>
+
+			<p>It's a simple website with a narrow scope. Enjoy. ʕ◔ϖ◔ʔ</p>
+		`)
+	if err != nil {
+		return err
+	}
+
+	_, err = io.WriteString(w, `<h3 style="margin-top: 30px;">Popular Packages</h3>`)
+	if err != nil {
+		return err
+	}
+
+	// Find some popular packages to display.
+	h.s.PackageIssuesMu.RLock()
+	pis := h.s.PackageIssues
+	h.s.PackageIssuesMu.RUnlock()
+	type pkg struct {
+		Path string
+		Open int
+	}
+	var popular []pkg
+	for _, p := range h.s.Packages {
+		popular = append(popular, pkg{
+			Path: p,
+			Open: len(pis[p].Open),
+		})
+	}
+	sort.SliceStable(popular, func(i, j int) bool { return popular[i].Open > popular[j].Open })
+	popular = popular[:15]
+
+	// Render the table.
+	_, err = io.WriteString(w, `<table class="table table-sm">
+		<thead>
+			<tr>
+				<th>Path</th>
+				<th>Open Issues</th>
+			</tr>
+		</thead>
+		<tbody>`)
+	if err != nil {
+		return err
+	}
+	for _, p := range popular {
+		err := html.Render(w, htmlg.TR(
+			htmlg.TD(htmlg.A(p.Path, "/"+p.Path)),
+			htmlg.TD(htmlg.Text(fmt.Sprint(p.Open))),
+		))
+		if err != nil {
+			return err
+		}
+	}
+	_, err = io.WriteString(w, `</tbody></table>`)
+	if err != nil {
+		return err
+	}
+
+	_, err = io.WriteString(w, htmlPart3)
+	return err
+}
+
+// ServeIssues serves a list of issues for the package with import path pkg.
+func (h *handler) ServeIssues(w http.ResponseWriter, req *http.Request, pkg string) error {
+	if req.Method != http.MethodGet {
+		return httperror.Method{Allowed: []string{http.MethodGet}}
+	}
+	filter, err := stateFilter(req.URL.Query())
+	if err != nil {
+		return httperror.BadRequest{Err: err}
+	}
+
+	h.s.PackageIssuesMu.RLock()
+	pi, ok := h.s.PackageIssues[pkg]
+	h.s.PackageIssuesMu.RUnlock()
+	if !ok {
+		return os.ErrNotExist
+	}
+	var is []issues.Issue
+	switch {
+	case filter == issues.StateFilter(issues.OpenState):
+		is = pi.Open
+	case filter == issues.StateFilter(issues.ClosedState):
+		is = pi.Closed
+	case filter == issues.AllStates:
+		is = append(pi.Open, pi.Closed...) // TODO: Measure if slow, optimize if needed.
+		sort.Slice(is, func(i, j int) bool { return is[i].ID > is[j].ID })
+	}
+	openCount := uint64(len(pi.Open))
+	closedCount := uint64(len(pi.Closed))
+
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	_, err = io.WriteString(w, htmlPart1)
+	if err != nil {
+		return err
+	}
+	_, err = w.Write(h.analyticsHTML)
+	if err != nil {
+		return err
+	}
+	_, err = io.WriteString(w, htmlPart2)
+	if err != nil {
+		return err
+	}
+	heading := htmlg.NodeComponent{
+		Type: html.ElementNode, Data: atom.H2.String(),
+		FirstChild: htmlg.Text(pkg),
+	}
+	title := pkg + ": "
+	if pkg == otherPackages {
+		title = ""
+	}
+	newIssue := htmlg.NodeComponent{
+		Type: html.ElementNode, Data: atom.Div.String(),
+		Attr:       []html.Attribute{{Key: atom.Style.String(), Val: "text-align: right;"}},
+		FirstChild: htmlg.A("New Issue", "https://golang.org/issue/new?title="+url.QueryEscape(title)),
+	}
+	var es []component.IssueEntry
+	for _, i := range is {
+		es = append(es, component.IssueEntry{Issue: i, BaseURI: "https://golang.org/issue"})
+	}
+	issues := component.Issues{
+		IssuesNav: component.IssuesNav{
+			OpenCount:     openCount,
+			ClosedCount:   closedCount,
+			Path:          req.URL.Path,
+			Query:         req.URL.Query(),
+			StateQueryKey: stateQueryKey,
+		},
+		Filter:  filter,
+		Entries: es,
+	}
+	err = htmlg.RenderComponents(w, heading, subheading{pkg}, newIssue, issues)
+	if err != nil {
+		return err
+	}
+	_, err = io.WriteString(w, htmlPart3)
+	return err
+}
+
+type subheading struct{ Pkg string }
+
+func (s subheading) Render() []*html.Node {
+	switch s.Pkg {
+	case otherPackages:
+		return []*html.Node{htmlg.P(htmlg.Text("Issues that don't fit into any existing Go package."))}
+	default:
+		return nil
+	}
+}
+
+const (
+	// stateQueryKey is name of query key for controlling issue state filter.
+	stateQueryKey = "state"
+)
+
+// stateFilter parses the issue state filter from query,
+// returning an error if the value is unsupported.
+func stateFilter(query url.Values) (issues.StateFilter, error) {
+	selectedTabName := query.Get(stateQueryKey)
+	switch selectedTabName {
+	case "":
+		return issues.StateFilter(issues.OpenState), nil
+	case "closed":
+		return issues.StateFilter(issues.ClosedState), nil
+	case "all":
+		return issues.AllStates, nil
+	default:
+		return "", fmt.Errorf("unsupported state filter value: %q", selectedTabName)
+	}
+}
+
+// stripPrefix returns request r with prefix of length prefixLen stripped from r.URL.Path.
+// prefixLen must not be longer than len(r.URL.Path), otherwise stripPrefix panics.
+// If r.URL.Path is empty after the prefix is stripped, the path is changed to "/".
+func stripPrefix(r *http.Request, prefixLen int) *http.Request {
+	r2 := new(http.Request)
+	*r2 = *r
+	r2.URL = new(url.URL)
+	*r2.URL = *r.URL
+	r2.URL.Path = r.URL.Path[prefixLen:]
+	if r2.URL.Path == "" {
+		r2.URL.Path = "/"
+	}
+	return r2
+}
packages.go
@@ -0,0 +1,820 @@
+package main
+
+// TODO: I was missing some golang.org/x/... packages in my GOPATH when generating
+//       this list, so it's incomplete.
+
+// existingPackages is a set of import paths of Go packages that are known to exist.
+// It includes packages in Go standard library and sub-repositories.
+//
+// The list is generated with:
+//
+// 	go list std cmd golang.org/x/...
+//
+var existingPackages = map[string]struct{}{
+	"archive/tar":                                             {},
+	"archive/zip":                                             {},
+	"bufio":                                                   {},
+	"bytes":                                                   {},
+	"cmd/addr2line":                                           {},
+	"cmd/api":                                                 {},
+	"cmd/asm":                                                 {},
+	"cmd/asm/internal/arch":                                   {},
+	"cmd/asm/internal/asm":                                    {},
+	"cmd/asm/internal/flags":                                  {},
+	"cmd/asm/internal/lex":                                    {},
+	"cmd/buildid":                                             {},
+	"cmd/cgo":                                                 {},
+	"cmd/compile":                                             {},
+	"cmd/compile/internal/amd64":                              {},
+	"cmd/compile/internal/arm":                                {},
+	"cmd/compile/internal/arm64":                              {},
+	"cmd/compile/internal/gc":                                 {},
+	"cmd/compile/internal/mips":                               {},
+	"cmd/compile/internal/mips64":                             {},
+	"cmd/compile/internal/ppc64":                              {},
+	"cmd/compile/internal/s390x":                              {},
+	"cmd/compile/internal/ssa":                                {},
+	"cmd/compile/internal/syntax":                             {},
+	"cmd/compile/internal/test":                               {},
+	"cmd/compile/internal/types":                              {},
+	"cmd/compile/internal/x86":                                {},
+	"cmd/cover":                                               {},
+	"cmd/dist":                                                {},
+	"cmd/doc":                                                 {},
+	"cmd/fix":                                                 {},
+	"cmd/go":                                                  {},
+	"cmd/go/internal/base":                                    {},
+	"cmd/go/internal/bug":                                     {},
+	"cmd/go/internal/cache":                                   {},
+	"cmd/go/internal/cfg":                                     {},
+	"cmd/go/internal/clean":                                   {},
+	"cmd/go/internal/cmdflag":                                 {},
+	"cmd/go/internal/doc":                                     {},
+	"cmd/go/internal/envcmd":                                  {},
+	"cmd/go/internal/fix":                                     {},
+	"cmd/go/internal/fmtcmd":                                  {},
+	"cmd/go/internal/generate":                                {},
+	"cmd/go/internal/get":                                     {},
+	"cmd/go/internal/help":                                    {},
+	"cmd/go/internal/list":                                    {},
+	"cmd/go/internal/load":                                    {},
+	"cmd/go/internal/run":                                     {},
+	"cmd/go/internal/str":                                     {},
+	"cmd/go/internal/test":                                    {},
+	"cmd/go/internal/tool":                                    {},
+	"cmd/go/internal/version":                                 {},
+	"cmd/go/internal/vet":                                     {},
+	"cmd/go/internal/web":                                     {},
+	"cmd/go/internal/work":                                    {},
+	"cmd/gofmt":                                               {},
+	"cmd/internal/bio":                                        {},
+	"cmd/internal/browser":                                    {},
+	"cmd/internal/buildid":                                    {},
+	"cmd/internal/dwarf":                                      {},
+	"cmd/internal/edit":                                       {},
+	"cmd/internal/gcprog":                                     {},
+	"cmd/internal/goobj":                                      {},
+	"cmd/internal/obj":                                        {},
+	"cmd/internal/obj/arm":                                    {},
+	"cmd/internal/obj/arm64":                                  {},
+	"cmd/internal/obj/mips":                                   {},
+	"cmd/internal/obj/ppc64":                                  {},
+	"cmd/internal/obj/s390x":                                  {},
+	"cmd/internal/obj/x86":                                    {},
+	"cmd/internal/objabi":                                     {},
+	"cmd/internal/objfile":                                    {},
+	"cmd/internal/src":                                        {},
+	"cmd/internal/sys":                                        {},
+	"cmd/internal/test2json":                                  {},
+	"cmd/link":                                                {},
+	"cmd/link/internal/amd64":                                 {},
+	"cmd/link/internal/arm":                                   {},
+	"cmd/link/internal/arm64":                                 {},
+	"cmd/link/internal/ld":                                    {},
+	"cmd/link/internal/loadelf":                               {},
+	"cmd/link/internal/loadmacho":                             {},
+	"cmd/link/internal/loadpe":                                {},
+	"cmd/link/internal/mips":                                  {},
+	"cmd/link/internal/mips64":                                {},
+	"cmd/link/internal/objfile":                               {},
+	"cmd/link/internal/ppc64":                                 {},
+	"cmd/link/internal/s390x":                                 {},
+	"cmd/link/internal/sym":                                   {},
+	"cmd/link/internal/x86":                                   {},
+	"cmd/nm":                                                  {},
+	"cmd/objdump":                                             {},
+	"cmd/pack":                                                {},
+	"cmd/pprof":                                               {},
+	"cmd/test2json":                                           {},
+	"cmd/trace":                                               {},
+	"cmd/vendor/github.com/google/pprof/driver":               {},
+	"cmd/vendor/github.com/google/pprof/internal/binutils":    {},
+	"cmd/vendor/github.com/google/pprof/internal/driver":      {},
+	"cmd/vendor/github.com/google/pprof/internal/elfexec":     {},
+	"cmd/vendor/github.com/google/pprof/internal/graph":       {},
+	"cmd/vendor/github.com/google/pprof/internal/measurement": {},
+	"cmd/vendor/github.com/google/pprof/internal/plugin":      {},
+	"cmd/vendor/github.com/google/pprof/internal/proftest":    {},
+	"cmd/vendor/github.com/google/pprof/internal/report":      {},
+	"cmd/vendor/github.com/google/pprof/internal/symbolizer":  {},
+	"cmd/vendor/github.com/google/pprof/internal/symbolz":     {},
+	"cmd/vendor/github.com/google/pprof/profile":              {},
+	"cmd/vendor/github.com/google/pprof/third_party/svg":      {},
+	"cmd/vendor/github.com/ianlancetaylor/demangle":           {},
+	"cmd/vendor/golang.org/x/arch/arm/armasm":                 {},
+	"cmd/vendor/golang.org/x/arch/arm64/arm64asm":             {},
+	"cmd/vendor/golang.org/x/arch/ppc64/ppc64asm":             {},
+	"cmd/vendor/golang.org/x/arch/x86/x86asm":                 {},
+	"cmd/vet":                           {},
+	"cmd/vet/internal/cfg":              {},
+	"cmd/vet/internal/whitelist":        {},
+	"compress/bzip2":                    {},
+	"compress/flate":                    {},
+	"compress/gzip":                     {},
+	"compress/lzw":                      {},
+	"compress/zlib":                     {},
+	"container/heap":                    {},
+	"container/list":                    {},
+	"container/ring":                    {},
+	"context":                           {},
+	"crypto":                            {},
+	"crypto/aes":                        {},
+	"crypto/cipher":                     {},
+	"crypto/des":                        {},
+	"crypto/dsa":                        {},
+	"crypto/ecdsa":                      {},
+	"crypto/elliptic":                   {},
+	"crypto/hmac":                       {},
+	"crypto/internal/cipherhw":          {},
+	"crypto/md5":                        {},
+	"crypto/rand":                       {},
+	"crypto/rc4":                        {},
+	"crypto/rsa":                        {},
+	"crypto/sha1":                       {},
+	"crypto/sha256":                     {},
+	"crypto/sha512":                     {},
+	"crypto/subtle":                     {},
+	"crypto/tls":                        {},
+	"crypto/x509":                       {},
+	"crypto/x509/pkix":                  {},
+	"database/sql":                      {},
+	"database/sql/driver":               {},
+	"debug/dwarf":                       {},
+	"debug/elf":                         {},
+	"debug/gosym":                       {},
+	"debug/macho":                       {},
+	"debug/pe":                          {},
+	"debug/plan9obj":                    {},
+	"encoding":                          {},
+	"encoding/ascii85":                  {},
+	"encoding/asn1":                     {},
+	"encoding/base32":                   {},
+	"encoding/base64":                   {},
+	"encoding/binary":                   {},
+	"encoding/csv":                      {},
+	"encoding/gob":                      {},
+	"encoding/hex":                      {},
+	"encoding/json":                     {},
+	"encoding/pem":                      {},
+	"encoding/xml":                      {},
+	"errors":                            {},
+	"expvar":                            {},
+	"flag":                              {},
+	"fmt":                               {},
+	"go/ast":                            {},
+	"go/build":                          {},
+	"go/constant":                       {},
+	"go/doc":                            {},
+	"go/format":                         {},
+	"go/importer":                       {},
+	"go/internal/gccgoimporter":         {},
+	"go/internal/gcimporter":            {},
+	"go/internal/srcimporter":           {},
+	"go/parser":                         {},
+	"go/printer":                        {},
+	"go/scanner":                        {},
+	"go/token":                          {},
+	"go/types":                          {},
+	"hash":                              {},
+	"hash/adler32":                      {},
+	"hash/crc32":                        {},
+	"hash/crc64":                        {},
+	"hash/fnv":                          {},
+	"html":                              {},
+	"html/template":                     {},
+	"image":                             {},
+	"image/color":                       {},
+	"image/color/palette":               {},
+	"image/draw":                        {},
+	"image/gif":                         {},
+	"image/internal/imageutil":          {},
+	"image/jpeg":                        {},
+	"image/png":                         {},
+	"index/suffixarray":                 {},
+	"internal/cpu":                      {},
+	"internal/nettrace":                 {},
+	"internal/poll":                     {},
+	"internal/race":                     {},
+	"internal/singleflight":             {},
+	"internal/syscall/windows":          {},
+	"internal/syscall/windows/registry": {},
+	"internal/syscall/windows/sysdll":   {},
+	"internal/testenv":                  {},
+	"internal/testlog":                  {},
+	"internal/trace":                    {},
+	"io":                                {},
+	"io/ioutil":                         {},
+	"log":                               {},
+	"log/syslog":                        {},
+	"math":                              {},
+	"math/big":                          {},
+	"math/bits":                         {},
+	"math/cmplx":                        {},
+	"math/rand":                         {},
+	"mime":                              {},
+	"mime/multipart":                    {},
+	"mime/quotedprintable":              {},
+	"net":                            {},
+	"net/http":                       {},
+	"net/http/cgi":                   {},
+	"net/http/cookiejar":             {},
+	"net/http/fcgi":                  {},
+	"net/http/httptest":              {},
+	"net/http/httptrace":             {},
+	"net/http/httputil":              {},
+	"net/http/internal":              {},
+	"net/http/pprof":                 {},
+	"net/internal/socktest":          {},
+	"net/mail":                       {},
+	"net/rpc":                        {},
+	"net/rpc/jsonrpc":                {},
+	"net/smtp":                       {},
+	"net/textproto":                  {},
+	"net/url":                        {},
+	"os":                             {},
+	"os/exec":                        {},
+	"os/signal":                      {},
+	"os/signal/internal/pty":         {},
+	"os/user":                        {},
+	"path":                           {},
+	"path/filepath":                  {},
+	"plugin":                         {},
+	"reflect":                        {},
+	"regexp":                         {},
+	"regexp/syntax":                  {},
+	"runtime":                        {},
+	"runtime/cgo":                    {},
+	"runtime/debug":                  {},
+	"runtime/internal/atomic":        {},
+	"runtime/internal/sys":           {},
+	"runtime/pprof":                  {},
+	"runtime/pprof/internal/profile": {},
+	"runtime/race":                   {},
+	"runtime/trace":                  {},
+	"sort":                           {},
+	"strconv":                        {},
+	"strings":                        {},
+	"sync":                           {},
+	"sync/atomic":                    {},
+	"syscall":                        {},
+	"testing":                        {},
+	"testing/internal/testdeps":      {},
+	"testing/iotest":                 {},
+	"testing/quick":                  {},
+	"text/scanner":                   {},
+	"text/tabwriter":                 {},
+	"text/template":                  {},
+	"text/template/parse":            {},
+	"time":                           {},
+	"unicode":                        {},
+	"unicode/utf16":                  {},
+	"unicode/utf8":                   {},
+	"unsafe":                         {},
+	"vendor/golang_org/x/crypto/chacha20poly1305":                   {},
+	"vendor/golang_org/x/crypto/chacha20poly1305/internal/chacha20": {},
+	"vendor/golang_org/x/crypto/cryptobyte":                         {},
+	"vendor/golang_org/x/crypto/cryptobyte/asn1":                    {},
+	"vendor/golang_org/x/crypto/curve25519":                         {},
+	"vendor/golang_org/x/crypto/poly1305":                           {},
+	"vendor/golang_org/x/net/http2/hpack":                           {},
+	"vendor/golang_org/x/net/idna":                                  {},
+	"vendor/golang_org/x/net/internal/nettest":                      {},
+	"vendor/golang_org/x/net/lex/httplex":                           {},
+	"vendor/golang_org/x/net/nettest":                               {},
+	"vendor/golang_org/x/net/proxy":                                 {},
+	"vendor/golang_org/x/net/route":                                 {},
+	"vendor/golang_org/x/text/secure":                               {},
+	"vendor/golang_org/x/text/secure/bidirule":                      {},
+	"vendor/golang_org/x/text/transform":                            {},
+	"vendor/golang_org/x/text/unicode":                              {},
+	"vendor/golang_org/x/text/unicode/bidi":                         {},
+	"vendor/golang_org/x/text/unicode/norm":                         {},
+
+	"golang.org/x/build":                                     {},
+	"golang.org/x/build/auth":                                {},
+	"golang.org/x/build/autocertcache":                       {},
+	"golang.org/x/build/buildenv":                            {},
+	"golang.org/x/build/buildlet":                            {},
+	"golang.org/x/build/cmd/builder":                         {},
+	"golang.org/x/build/cmd/buildlet":                        {},
+	"golang.org/x/build/cmd/buildlet/stage0":                 {},
+	"golang.org/x/build/cmd/buildstats":                      {},
+	"golang.org/x/build/cmd/cl":                              {},
+	"golang.org/x/build/cmd/coordinator":                     {},
+	"golang.org/x/build/cmd/coordinator/buildongce":          {},
+	"golang.org/x/build/cmd/coordinator/metrics":             {},
+	"golang.org/x/build/cmd/coordinator/spanlog":             {},
+	"golang.org/x/build/cmd/debugnewvm":                      {},
+	"golang.org/x/build/cmd/docker2boot":                     {},
+	"golang.org/x/build/cmd/fetchlogs":                       {},
+	"golang.org/x/build/cmd/genbootstrap":                    {},
+	"golang.org/x/build/cmd/gerritbot":                       {},
+	"golang.org/x/build/cmd/gitlock":                         {},
+	"golang.org/x/build/cmd/gitmirror":                       {},
+	"golang.org/x/build/cmd/gomote":                          {},
+	"golang.org/x/build/cmd/gopherbot":                       {},
+	"golang.org/x/build/cmd/gopherstats":                     {},
+	"golang.org/x/build/cmd/makemac":                         {},
+	"golang.org/x/build/cmd/perfrun":                         {},
+	"golang.org/x/build/cmd/pubsubhelper":                    {},
+	"golang.org/x/build/cmd/pubsubhelper/pubsubtypes":        {},
+	"golang.org/x/build/cmd/pushback":                        {},
+	"golang.org/x/build/cmd/racebuild":                       {},
+	"golang.org/x/build/cmd/release":                         {},
+	"golang.org/x/build/cmd/releasebot":                      {},
+	"golang.org/x/build/cmd/relnote":                         {},
+	"golang.org/x/build/cmd/retrybuilds":                     {},
+	"golang.org/x/build/cmd/rundockerbuildlet":               {},
+	"golang.org/x/build/cmd/scaleway":                        {},
+	"golang.org/x/build/cmd/upload":                          {},
+	"golang.org/x/build/dashboard":                           {},
+	"golang.org/x/build/devapp":                              {},
+	"golang.org/x/build/envutil":                             {},
+	"golang.org/x/build/gerrit":                              {},
+	"golang.org/x/build/internal/buildgo":                    {},
+	"golang.org/x/build/internal/buildstats":                 {},
+	"golang.org/x/build/internal/gitauth":                    {},
+	"golang.org/x/build/internal/gophers":                    {},
+	"golang.org/x/build/internal/httpdl":                     {},
+	"golang.org/x/build/internal/https":                      {},
+	"golang.org/x/build/internal/loghash":                    {},
+	"golang.org/x/build/internal/lru":                        {},
+	"golang.org/x/build/internal/singleflight":               {},
+	"golang.org/x/build/internal/sourcecache":                {},
+	"golang.org/x/build/internal/untar":                      {},
+	"golang.org/x/build/kubernetes":                          {},
+	"golang.org/x/build/kubernetes/api":                      {},
+	"golang.org/x/build/kubernetes/gke":                      {},
+	"golang.org/x/build/livelog":                             {},
+	"golang.org/x/build/maintner":                            {},
+	"golang.org/x/build/maintner/cmd/maintserve":             {},
+	"golang.org/x/build/maintner/godata":                     {},
+	"golang.org/x/build/maintner/gostats":                    {},
+	"golang.org/x/build/maintner/maintnerd":                  {},
+	"golang.org/x/build/maintner/maintnerd/apipb":            {},
+	"golang.org/x/build/maintner/maintpb":                    {},
+	"golang.org/x/build/maintner/maintq":                     {},
+	"golang.org/x/build/maintner/reclog":                     {},
+	"golang.org/x/build/pargzip":                             {},
+	"golang.org/x/build/revdial":                             {},
+	"golang.org/x/build/status":                              {},
+	"golang.org/x/build/status/statusserver":                 {},
+	"golang.org/x/build/tarutil":                             {},
+	"golang.org/x/build/types":                               {},
+	"golang.org/x/build/vcs-test/vcweb":                      {},
+	"golang.org/x/build/version":                             {},
+	"golang.org/x/build/version/go1.10":                      {},
+	"golang.org/x/build/version/go1.10beta1":                 {},
+	"golang.org/x/build/version/go1.10beta2":                 {},
+	"golang.org/x/build/version/go1.10rc1":                   {},
+	"golang.org/x/build/version/go1.10rc2":                   {},
+	"golang.org/x/build/version/go1.8":                       {},
+	"golang.org/x/build/version/go1.8.1":                     {},
+	"golang.org/x/build/version/go1.8.2":                     {},
+	"golang.org/x/build/version/go1.8.3":                     {},
+	"golang.org/x/build/version/go1.8.4":                     {},
+	"golang.org/x/build/version/go1.8.5":                     {},
+	"golang.org/x/build/version/go1.8.6":                     {},
+	"golang.org/x/build/version/go1.8.7":                     {},
+	"golang.org/x/build/version/go1.8beta1":                  {},
+	"golang.org/x/build/version/go1.8beta2":                  {},
+	"golang.org/x/build/version/go1.8rc1":                    {},
+	"golang.org/x/build/version/go1.8rc2":                    {},
+	"golang.org/x/build/version/go1.8rc3":                    {},
+	"golang.org/x/build/version/go1.9":                       {},
+	"golang.org/x/build/version/go1.9.1":                     {},
+	"golang.org/x/build/version/go1.9.2":                     {},
+	"golang.org/x/build/version/go1.9.3":                     {},
+	"golang.org/x/build/version/go1.9.4":                     {},
+	"golang.org/x/build/version/go1.9beta1":                  {},
+	"golang.org/x/build/version/go1.9beta2":                  {},
+	"golang.org/x/build/version/go1.9rc1":                    {},
+	"golang.org/x/build/version/go1.9rc2":                    {},
+	"golang.org/x/build/version/internal/genv":               {},
+	"golang.org/x/crypto/acme":                               {},
+	"golang.org/x/crypto/acme/autocert":                      {},
+	"golang.org/x/crypto/argon2":                             {},
+	"golang.org/x/crypto/bcrypt":                             {},
+	"golang.org/x/crypto/blake2b":                            {},
+	"golang.org/x/crypto/blake2s":                            {},
+	"golang.org/x/crypto/blowfish":                           {},
+	"golang.org/x/crypto/bn256":                              {},
+	"golang.org/x/crypto/cast5":                              {},
+	"golang.org/x/crypto/chacha20poly1305":                   {},
+	"golang.org/x/crypto/cryptobyte":                         {},
+	"golang.org/x/crypto/cryptobyte/asn1":                    {},
+	"golang.org/x/crypto/curve25519":                         {},
+	"golang.org/x/crypto/ed25519":                            {},
+	"golang.org/x/crypto/ed25519/internal/edwards25519":      {},
+	"golang.org/x/crypto/hkdf":                               {},
+	"golang.org/x/crypto/internal/chacha20":                  {},
+	"golang.org/x/crypto/md4":                                {},
+	"golang.org/x/crypto/nacl/auth":                          {},
+	"golang.org/x/crypto/nacl/box":                           {},
+	"golang.org/x/crypto/nacl/secretbox":                     {},
+	"golang.org/x/crypto/ocsp":                               {},
+	"golang.org/x/crypto/openpgp":                            {},
+	"golang.org/x/crypto/openpgp/armor":                      {},
+	"golang.org/x/crypto/openpgp/clearsign":                  {},
+	"golang.org/x/crypto/openpgp/elgamal":                    {},
+	"golang.org/x/crypto/openpgp/errors":                     {},
+	"golang.org/x/crypto/openpgp/packet":                     {},
+	"golang.org/x/crypto/openpgp/s2k":                        {},
+	"golang.org/x/crypto/otr":                                {},
+	"golang.org/x/crypto/pbkdf2":                             {},
+	"golang.org/x/crypto/pkcs12":                             {},
+	"golang.org/x/crypto/pkcs12/internal/rc2":                {},
+	"golang.org/x/crypto/poly1305":                           {},
+	"golang.org/x/crypto/ripemd160":                          {},
+	"golang.org/x/crypto/salsa20":                            {},
+	"golang.org/x/crypto/salsa20/salsa":                      {},
+	"golang.org/x/crypto/scrypt":                             {},
+	"golang.org/x/crypto/sha3":                               {},
+	"golang.org/x/crypto/ssh":                                {},
+	"golang.org/x/crypto/ssh/agent":                          {},
+	"golang.org/x/crypto/ssh/knownhosts":                     {},
+	"golang.org/x/crypto/ssh/terminal":                       {},
+	"golang.org/x/crypto/ssh/test":                           {},
+	"golang.org/x/crypto/tea":                                {},
+	"golang.org/x/crypto/twofish":                            {},
+	"golang.org/x/crypto/xtea":                               {},
+	"golang.org/x/crypto/xts":                                {},
+	"golang.org/x/exp/ebnf":                                  {},
+	"golang.org/x/exp/ebnflint":                              {},
+	"golang.org/x/exp/io/i2c":                                {},
+	"golang.org/x/exp/io/i2c/driver":                         {},
+	"golang.org/x/exp/io/i2c/example/displayip":              {},
+	"golang.org/x/exp/io/spi":                                {},
+	"golang.org/x/exp/io/spi/driver":                         {},
+	"golang.org/x/exp/mmap":                                  {},
+	"golang.org/x/exp/old/netchan":                           {},
+	"golang.org/x/exp/rand":                                  {},
+	"golang.org/x/exp/shiny/driver":                          {},
+	"golang.org/x/exp/shiny/driver/gldriver":                 {},
+	"golang.org/x/exp/shiny/driver/internal/drawer":          {},
+	"golang.org/x/exp/shiny/driver/internal/errscreen":       {},
+	"golang.org/x/exp/shiny/driver/internal/event":           {},
+	"golang.org/x/exp/shiny/driver/internal/lifecycler":      {},
+	"golang.org/x/exp/shiny/driver/internal/swizzle":         {},
+	"golang.org/x/exp/shiny/driver/internal/win32":           {},
+	"golang.org/x/exp/shiny/driver/internal/x11key":          {},
+	"golang.org/x/exp/shiny/driver/windriver":                {},
+	"golang.org/x/exp/shiny/driver/x11driver":                {},
+	"golang.org/x/exp/shiny/gesture":                         {},
+	"golang.org/x/exp/shiny/iconvg":                          {},
+	"golang.org/x/exp/shiny/iconvg/internal/gradient":        {},
+	"golang.org/x/exp/shiny/imageutil":                       {},
+	"golang.org/x/exp/shiny/materialdesign/colornames":       {},
+	"golang.org/x/exp/shiny/materialdesign/icons":            {},
+	"golang.org/x/exp/shiny/screen":                          {},
+	"golang.org/x/exp/shiny/text":                            {},
+	"golang.org/x/exp/shiny/unit":                            {},
+	"golang.org/x/exp/shiny/widget":                          {},
+	"golang.org/x/exp/shiny/widget/flex":                     {},
+	"golang.org/x/exp/shiny/widget/glwidget":                 {},
+	"golang.org/x/exp/shiny/widget/node":                     {},
+	"golang.org/x/exp/shiny/widget/theme":                    {},
+	"golang.org/x/exp/utf8string":                            {},
+	"golang.org/x/image/bmp":                                 {},
+	"golang.org/x/image/colornames":                          {},
+	"golang.org/x/image/draw":                                {},
+	"golang.org/x/image/font":                                {},
+	"golang.org/x/image/font/basicfont":                      {},
+	"golang.org/x/image/font/gofont/gobold":                  {},
+	"golang.org/x/image/font/gofont/gobolditalic":            {},
+	"golang.org/x/image/font/gofont/goitalic":                {},
+	"golang.org/x/image/font/gofont/gomedium":                {},
+	"golang.org/x/image/font/gofont/gomediumitalic":          {},
+	"golang.org/x/image/font/gofont/gomono":                  {},
+	"golang.org/x/image/font/gofont/gomonobold":              {},
+	"golang.org/x/image/font/gofont/gomonobolditalic":        {},
+	"golang.org/x/image/font/gofont/gomonoitalic":            {},
+	"golang.org/x/image/font/gofont/goregular":               {},
+	"golang.org/x/image/font/gofont/gosmallcaps":             {},
+	"golang.org/x/image/font/gofont/gosmallcapsitalic":       {},
+	"golang.org/x/image/font/inconsolata":                    {},
+	"golang.org/x/image/font/opentype":                       {},
+	"golang.org/x/image/font/plan9font":                      {},
+	"golang.org/x/image/font/sfnt":                           {},
+	"golang.org/x/image/math/f32":                            {},
+	"golang.org/x/image/math/f64":                            {},
+	"golang.org/x/image/math/fixed":                          {},
+	"golang.org/x/image/riff":                                {},
+	"golang.org/x/image/tiff":                                {},
+	"golang.org/x/image/tiff/lzw":                            {},
+	"golang.org/x/image/vector":                              {},
+	"golang.org/x/image/vp8":                                 {},
+	"golang.org/x/image/vp8l":                                {},
+	"golang.org/x/image/webp":                                {},
+	"golang.org/x/image/webp/nycbcra":                        {},
+	"golang.org/x/lint":                                      {},
+	"golang.org/x/lint/golint":                               {},
+	"golang.org/x/mobile/app":                                {},
+	"golang.org/x/mobile/app/internal/apptest":               {},
+	"golang.org/x/mobile/app/internal/testapp":               {},
+	"golang.org/x/mobile/asset":                              {},
+	"golang.org/x/mobile/bind":                               {},
+	"golang.org/x/mobile/bind/benchmark":                     {},
+	"golang.org/x/mobile/bind/java":                          {},
+	"golang.org/x/mobile/bind/objc":                          {},
+	"golang.org/x/mobile/bind/seq":                           {},
+	"golang.org/x/mobile/bind/testpkg":                       {},
+	"golang.org/x/mobile/bind/testpkg/secondpkg":             {},
+	"golang.org/x/mobile/bind/testpkg/simplepkg":             {},
+	"golang.org/x/mobile/bind/testpkg/unboundpkg":            {},
+	"golang.org/x/mobile/cmd/gobind":                         {},
+	"golang.org/x/mobile/cmd/gomobile":                       {},
+	"golang.org/x/mobile/event/key":                          {},
+	"golang.org/x/mobile/event/lifecycle":                    {},
+	"golang.org/x/mobile/event/mouse":                        {},
+	"golang.org/x/mobile/event/paint":                        {},
+	"golang.org/x/mobile/event/size":                         {},
+	"golang.org/x/mobile/event/touch":                        {},
+	"golang.org/x/mobile/example/basic":                      {},
+	"golang.org/x/mobile/example/bind/hello":                 {},
+	"golang.org/x/mobile/example/flappy":                     {},
+	"golang.org/x/mobile/example/network":                    {},
+	"golang.org/x/mobile/exp/app/debug":                      {},
+	"golang.org/x/mobile/exp/audio/al":                       {},
+	"golang.org/x/mobile/exp/f32":                            {},
+	"golang.org/x/mobile/exp/font":                           {},
+	"golang.org/x/mobile/exp/gl/glutil":                      {},
+	"golang.org/x/mobile/exp/sensor":                         {},
+	"golang.org/x/mobile/exp/sprite":                         {},
+	"golang.org/x/mobile/exp/sprite/clock":                   {},
+	"golang.org/x/mobile/exp/sprite/glsprite":                {},
+	"golang.org/x/mobile/exp/sprite/portable":                {},
+	"golang.org/x/mobile/geom":                               {},
+	"golang.org/x/mobile/gl":                                 {},
+	"golang.org/x/mobile/internal/binres":                    {},
+	"golang.org/x/mobile/internal/importers":                 {},
+	"golang.org/x/mobile/internal/importers/java":            {},
+	"golang.org/x/mobile/internal/importers/objc":            {},
+	"golang.org/x/mobile/internal/mobileinit":                {},
+	"golang.org/x/net/bpf":                                   {},
+	"golang.org/x/net/context":                               {},
+	"golang.org/x/net/context/ctxhttp":                       {},
+	"golang.org/x/net/dict":                                  {},
+	"golang.org/x/net/dns/dnsmessage":                        {},
+	"golang.org/x/net/html":                                  {},
+	"golang.org/x/net/html/atom":                             {},
+	"golang.org/x/net/html/charset":                          {},
+	"golang.org/x/net/http/httpproxy":                        {},
+	"golang.org/x/net/http2":                                 {},
+	"golang.org/x/net/http2/h2i":                             {},
+	"golang.org/x/net/http2/hpack":                           {},
+	"golang.org/x/net/icmp":                                  {},
+	"golang.org/x/net/idna":                                  {},
+	"golang.org/x/net/internal/iana":                         {},
+	"golang.org/x/net/internal/nettest":                      {},
+	"golang.org/x/net/internal/socket":                       {},
+	"golang.org/x/net/internal/timeseries":                   {},
+	"golang.org/x/net/ipv4":                                  {},
+	"golang.org/x/net/ipv6":                                  {},
+	"golang.org/x/net/lex/httplex":                           {},
+	"golang.org/x/net/nettest":                               {},
+	"golang.org/x/net/netutil":                               {},
+	"golang.org/x/net/proxy":                                 {},
+	"golang.org/x/net/publicsuffix":                          {},
+	"golang.org/x/net/route":                                 {},
+	"golang.org/x/net/trace":                                 {},
+	"golang.org/x/net/webdav":                                {},
+	"golang.org/x/net/webdav/internal/xml":                   {},
+	"golang.org/x/net/websocket":                             {},
+	"golang.org/x/net/xsrftoken":                             {},
+	"golang.org/x/oauth2":                                    {},
+	"golang.org/x/oauth2/amazon":                             {},
+	"golang.org/x/oauth2/bitbucket":                          {},
+	"golang.org/x/oauth2/clientcredentials":                  {},
+	"golang.org/x/oauth2/facebook":                           {},
+	"golang.org/x/oauth2/fitbit":                             {},
+	"golang.org/x/oauth2/foursquare":                         {},
+	"golang.org/x/oauth2/github":                             {},
+	"golang.org/x/oauth2/google":                             {},
+	"golang.org/x/oauth2/heroku":                             {},
+	"golang.org/x/oauth2/hipchat":                            {},
+	"golang.org/x/oauth2/internal":                           {},
+	"golang.org/x/oauth2/jws":                                {},
+	"golang.org/x/oauth2/jwt":                                {},
+	"golang.org/x/oauth2/linkedin":                           {},
+	"golang.org/x/oauth2/mailchimp":                          {},
+	"golang.org/x/oauth2/mailru":                             {},
+	"golang.org/x/oauth2/mediamath":                          {},
+	"golang.org/x/oauth2/microsoft":                          {},
+	"golang.org/x/oauth2/odnoklassniki":                      {},
+	"golang.org/x/oauth2/paypal":                             {},
+	"golang.org/x/oauth2/slack":                              {},
+	"golang.org/x/oauth2/spotify":                            {},
+	"golang.org/x/oauth2/twitch":                             {},
+	"golang.org/x/oauth2/uber":                               {},
+	"golang.org/x/oauth2/vk":                                 {},
+	"golang.org/x/oauth2/yahoo":                              {},
+	"golang.org/x/oauth2/yandex":                             {},
+	"golang.org/x/perf/analysis/app":                         {},
+	"golang.org/x/perf/analysis/localperf":                   {},
+	"golang.org/x/perf/benchstat":                            {},
+	"golang.org/x/perf/cmd/benchsave":                        {},
+	"golang.org/x/perf/cmd/benchstat":                        {},
+	"golang.org/x/perf/internal/basedir":                     {},
+	"golang.org/x/perf/internal/diff":                        {},
+	"golang.org/x/perf/internal/stats":                       {},
+	"golang.org/x/perf/storage":                              {},
+	"golang.org/x/perf/storage/app":                          {},
+	"golang.org/x/perf/storage/benchfmt":                     {},
+	"golang.org/x/perf/storage/db":                           {},
+	"golang.org/x/perf/storage/db/dbtest":                    {},
+	"golang.org/x/perf/storage/db/sqlite3":                   {},
+	"golang.org/x/perf/storage/fs":                           {},
+	"golang.org/x/perf/storage/fs/gcs":                       {},
+	"golang.org/x/perf/storage/fs/local":                     {},
+	"golang.org/x/perf/storage/localperfdata":                {},
+	"golang.org/x/perf/storage/query":                        {},
+	"golang.org/x/review/git-codereview":                     {},
+	"golang.org/x/sync/errgroup":                             {},
+	"golang.org/x/sync/semaphore":                            {},
+	"golang.org/x/sync/singleflight":                         {},
+	"golang.org/x/sync/syncmap":                              {},
+	"golang.org/x/sys/unix":                                  {},
+	"golang.org/x/sys/windows":                               {},
+	"golang.org/x/sys/windows/registry":                      {},
+	"golang.org/x/talks/2014/go4java/runner":                 {},
+	"golang.org/x/talks/2014/organizeio":                     {},
+	"golang.org/x/talks/2014/research2":                      {},
+	"golang.org/x/talks/2014/static-analysis/egtest":         {},
+	"golang.org/x/talks/2014/taste":                          {},
+	"golang.org/x/talks/2014/testing/subprocess":             {},
+	"golang.org/x/talks/2014/testing/test1":                  {},
+	"golang.org/x/talks/2014/testing/test2":                  {},
+	"golang.org/x/talks/2015/keeping-up":                     {},
+	"golang.org/x/talks/2015/tricks/broadcastwriter":         {},
+	"golang.org/x/talks/2015/tricks/subprocess":              {},
+	"golang.org/x/talks/2016/applicative":                    {},
+	"golang.org/x/talks/2016/applicative/google":             {},
+	"golang.org/x/talks/2016/asm":                            {},
+	"golang.org/x/talks/2017/state-of-go-may/alias":          {},
+	"golang.org/x/talks/2017/state-of-go-may/bits":           {},
+	"golang.org/x/talks/2017/state-of-go-may/exec":           {},
+	"golang.org/x/talks/2017/state-of-go-may/exec/getenv":    {},
+	"golang.org/x/talks/2017/state-of-go-may/html":           {},
+	"golang.org/x/talks/2017/state-of-go-may/syncmap":        {},
+	"golang.org/x/talks/2017/state-of-go/stdlib/sort":        {},
+	"golang.org/x/text":                                      {},
+	"golang.org/x/text/cases":                                {},
+	"golang.org/x/text/cmd/gotext":                           {},
+	"golang.org/x/text/cmd/gotext/examples/extract":          {},
+	"golang.org/x/text/cmd/gotext/examples/extract_http":     {},
+	"golang.org/x/text/cmd/gotext/examples/extract_http/pkg": {},
+	"golang.org/x/text/cmd/gotext/examples/rewrite":          {},
+	"golang.org/x/text/collate":                              {},
+	"golang.org/x/text/collate/build":                        {},
+	"golang.org/x/text/collate/tools/colcmp":                 {},
+	"golang.org/x/text/currency":                             {},
+	"golang.org/x/text/date":                                 {},
+	"golang.org/x/text/encoding":                             {},
+	"golang.org/x/text/encoding/charmap":                     {},
+	"golang.org/x/text/encoding/htmlindex":                   {},
+	"golang.org/x/text/encoding/ianaindex":                   {},
+	"golang.org/x/text/encoding/internal":                    {},
+	"golang.org/x/text/encoding/internal/enctest":            {},
+	"golang.org/x/text/encoding/internal/identifier":         {},
+	"golang.org/x/text/encoding/japanese":                    {},
+	"golang.org/x/text/encoding/korean":                      {},
+	"golang.org/x/text/encoding/simplifiedchinese":           {},
+	"golang.org/x/text/encoding/traditionalchinese":          {},
+	"golang.org/x/text/encoding/unicode":                     {},
+	"golang.org/x/text/encoding/unicode/utf32":               {},
+	"golang.org/x/text/feature/plural":                       {},
+	"golang.org/x/text/internal":                             {},
+	"golang.org/x/text/internal/catmsg":                      {},
+	"golang.org/x/text/internal/cldrtree":                    {},
+	"golang.org/x/text/internal/colltab":                     {},
+	"golang.org/x/text/internal/export/idna":                 {},
+	"golang.org/x/text/internal/format":                      {},
+	"golang.org/x/text/internal/gen":                         {},
+	"golang.org/x/text/internal/gen/bitfield":                {},
+	"golang.org/x/text/internal/language":                    {},
+	"golang.org/x/text/internal/language/compact":            {},
+	"golang.org/x/text/internal/number":                      {},
+	"golang.org/x/text/internal/stringset":                   {},
+	"golang.org/x/text/internal/tag":                         {},
+	"golang.org/x/text/internal/testtext":                    {},
+	"golang.org/x/text/internal/triegen":                     {},
+	"golang.org/x/text/internal/ucd":                         {},
+	"golang.org/x/text/internal/utf8internal":                {},
+	"golang.org/x/text/language":                             {},
+	"golang.org/x/text/language/display":                     {},
+	"golang.org/x/text/message":                              {},
+	"golang.org/x/text/message/catalog":                      {},
+	"golang.org/x/text/message/pipeline":                     {},
+	"golang.org/x/text/number":                               {},
+	"golang.org/x/text/runes":                                {},
+	"golang.org/x/text/search":                               {},
+	"golang.org/x/text/secure":                               {},
+	"golang.org/x/text/secure/bidirule":                      {},
+	"golang.org/x/text/secure/precis":                        {},
+	"golang.org/x/text/transform":                            {},
+	"golang.org/x/text/unicode":                              {},
+	"golang.org/x/text/unicode/bidi":                         {},
+	"golang.org/x/text/unicode/cldr":                         {},
+	"golang.org/x/text/unicode/norm":                         {},
+	"golang.org/x/text/unicode/rangetable":                   {},
+	"golang.org/x/text/unicode/runenames":                    {},
+	"golang.org/x/text/width":                                {},
+	"golang.org/x/tools/benchmark/parse":                     {},
+	"golang.org/x/tools/blog":                                {},
+	"golang.org/x/tools/blog/atom":                           {},
+	"golang.org/x/tools/cmd/benchcmp":                        {},
+	"golang.org/x/tools/cmd/bundle":                          {},
+	"golang.org/x/tools/cmd/callgraph":                       {},
+	"golang.org/x/tools/cmd/compilebench":                    {},
+	"golang.org/x/tools/cmd/cover":                           {},
+	"golang.org/x/tools/cmd/digraph":                         {},
+	"golang.org/x/tools/cmd/eg":                              {},
+	"golang.org/x/tools/cmd/fiximports":                      {},
+	"golang.org/x/tools/cmd/getgo":                           {},
+	"golang.org/x/tools/cmd/getgo/server":                    {},
+	"golang.org/x/tools/cmd/go-contrib-init":                 {},
+	"golang.org/x/tools/cmd/godex":                           {},
+	"golang.org/x/tools/cmd/godoc":                           {},
+	"golang.org/x/tools/cmd/goimports":                       {},
+	"golang.org/x/tools/cmd/gomvpkg":                         {},
+	"golang.org/x/tools/cmd/gorename":                        {},
+	"golang.org/x/tools/cmd/gotype":                          {},
+	"golang.org/x/tools/cmd/goyacc":                          {},
+	"golang.org/x/tools/cmd/guru":                            {},
+	"golang.org/x/tools/cmd/guru/serial":                     {},
+	"golang.org/x/tools/cmd/heapview":                        {},
+	"golang.org/x/tools/cmd/heapview/internal/core":          {},
+	"golang.org/x/tools/cmd/html2article":                    {},
+	"golang.org/x/tools/cmd/present":                         {},
+	"golang.org/x/tools/cmd/ssadump":                         {},
+	"golang.org/x/tools/cmd/stress":                          {},
+	"golang.org/x/tools/cmd/stringer":                        {},
+	"golang.org/x/tools/cmd/tip":                             {},
+	"golang.org/x/tools/cmd/toolstash":                       {},
+	"golang.org/x/tools/container/intsets":                   {},
+	"golang.org/x/tools/cover":                               {},
+	"golang.org/x/tools/go/ast/astutil":                      {},
+	"golang.org/x/tools/go/buildutil":                        {},
+	"golang.org/x/tools/go/callgraph":                        {},
+	"golang.org/x/tools/go/callgraph/cha":                    {},
+	"golang.org/x/tools/go/callgraph/rta":                    {},
+	"golang.org/x/tools/go/callgraph/static":                 {},
+	"golang.org/x/tools/go/gccgoexportdata":                  {},
+	"golang.org/x/tools/go/gcexportdata":                     {},
+	"golang.org/x/tools/go/gcimporter15":                     {},
+	"golang.org/x/tools/go/internal/gccgoimporter":           {},
+	"golang.org/x/tools/go/loader":                           {},
+	"golang.org/x/tools/go/pointer":                          {},
+	"golang.org/x/tools/go/ssa":                              {},
+	"golang.org/x/tools/go/ssa/interp":                       {},
+	"golang.org/x/tools/go/ssa/ssautil":                      {},
+	"golang.org/x/tools/go/types/typeutil":                   {},
+	"golang.org/x/tools/go/vcs":                              {},
+	"golang.org/x/tools/godoc":                               {},
+	"golang.org/x/tools/godoc/analysis":                      {},
+	"golang.org/x/tools/godoc/redirect":                      {},
+	"golang.org/x/tools/godoc/static":                        {},
+	"golang.org/x/tools/godoc/util":                          {},
+	"golang.org/x/tools/godoc/vfs":                           {},
+	"golang.org/x/tools/godoc/vfs/gatefs":                    {},
+	"golang.org/x/tools/godoc/vfs/httpfs":                    {},
+	"golang.org/x/tools/godoc/vfs/mapfs":                     {},
+	"golang.org/x/tools/godoc/vfs/zipfs":                     {},
+	"golang.org/x/tools/imports":                             {},
+	"golang.org/x/tools/playground":                          {},
+	"golang.org/x/tools/playground/socket":                   {},
+	"golang.org/x/tools/present":                             {},
+	"golang.org/x/tools/refactor/eg":                         {},
+	"golang.org/x/tools/refactor/importgraph":                {},
+	"golang.org/x/tools/refactor/rename":                     {},
+	"golang.org/x/tools/refactor/satisfy":                    {},
+	"golang.org/x/tour/content":                              {},
+	"golang.org/x/tour/gotour":                               {},
+	"golang.org/x/tour/pic":                                  {},
+	"golang.org/x/tour/reader":                               {},
+	"golang.org/x/tour/tree":                                 {},
+	"golang.org/x/tour/wc":                                   {},
+
+	"golang.org/x/vgo": {},
+}
service.go
@@ -0,0 +1,280 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"path"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/shurcooL/issues"
+	"github.com/shurcooL/users"
+	"golang.org/x/build/maintner"
+	"golang.org/x/build/maintner/godata"
+)
+
+type service struct {
+	// PackageIssues contains issues for all packages. Map key is import path.
+	// An additional entry with key otherPackages is for issues that don't fit
+	// into any existing Go package.
+	PackageIssuesMu sync.RWMutex
+	PackageIssues   map[string]*pkgIssues
+
+	// Packages is a list of all packages. Sorted by import path, standard library first.
+	Packages []string
+}
+
+type pkgIssues struct {
+	Open, Closed []issues.Issue
+}
+
+func newService() *service {
+	packageIssues := emptyPackages()
+
+	// Initialize list of packages sorted by import path, standard library first.
+	var packages []string
+	for p := range packageIssues {
+		if p == otherPackages { // Don't include "other", it's not a real package.
+			continue
+		}
+		packages = append(packages, p)
+	}
+	sort.Slice(packages, func(i, j int) bool {
+		if a, b := category(packages[i]), category(packages[j]); a != b {
+			return a < b
+		}
+		return packages[i] < packages[j]
+	})
+
+	s := &service{
+		PackageIssues: packageIssues,
+		Packages:      packages,
+	}
+	go s.poll()
+	return s
+}
+
+func emptyPackages() map[string]*pkgIssues {
+	// Initialize places for issues, using existing packages
+	// and their parent directories.
+	packageIssues := make(map[string]*pkgIssues)
+	for p := range existingPackages {
+		elems := strings.Split(p, "/")
+		for i := len(elems); i >= 1; i-- { // Iterate in reverse order so we can break out early.
+			p := path.Join(elems[:i]...)
+			if _, ok := packageIssues[p]; ok {
+				break
+			}
+			packageIssues[p] = new(pkgIssues)
+		}
+	}
+	packageIssues[otherPackages] = new(pkgIssues)
+	return packageIssues
+}
+
+func category(importPath string) int {
+	switch isStandard(importPath) {
+	case true:
+		return 0
+	case false:
+		return 1
+	default:
+		panic("unreachable")
+	}
+}
+
+// isStandard reports whether import path p is in standard library.
+// It's determined by whether the first '/'-separated element contains a dot.
+func isStandard(p string) bool {
+	if i := strings.IndexByte(p, '/'); i != -1 {
+		p = p[:i]
+	}
+	return !strings.Contains(p, ".")
+}
+
+func (s *service) poll() {
+	corpus, repo, err := initCorpus()
+	if err != nil {
+		log.Fatalln("poll: initial initCorpus failed:", err)
+	}
+
+	for {
+		packageIssues := packageIssues(repo)
+		s.PackageIssuesMu.Lock()
+		s.PackageIssues = packageIssues
+		s.PackageIssuesMu.Unlock()
+		for {
+			started := time.Now()
+			updateError := corpus.Update(context.Background())
+			if updateError == maintner.ErrSplit {
+				log.Println("corpus.Update: Corpus out of sync. Re-fetching corpus.")
+				corpus, repo, err = initCorpus()
+				if err != nil {
+					log.Fatalln("poll: post-ErrSplit initCorpus failed:", err)
+				}
+			} else if updateError != nil {
+				log.Printf("corpus.Update: %v; sleeping 15s", updateError)
+				time.Sleep(15 * time.Second)
+				continue
+			}
+			log.Printf("got corpus update after %v", time.Since(started))
+			break
+		}
+	}
+}
+
+func initCorpus() (*maintner.Corpus, *maintner.GitHubRepo, error) {
+	corpus, err := godata.Get(context.Background())
+	if err != nil {
+		return nil, nil, fmt.Errorf("godata.Get: %v", err)
+	}
+	repo := corpus.GitHub().Repo("golang", "go")
+	if repo == nil {
+		return nil, nil, fmt.Errorf("golang/go repo not found")
+	}
+	return corpus, repo, nil
+}
+
+func packageIssues(repo *maintner.GitHubRepo) map[string]*pkgIssues {
+	packageIssues := emptyPackages()
+	err := repo.ForeachIssue(func(i *maintner.GitHubIssue) error {
+		if i.NotExist || i.PullRequest {
+			return nil
+		}
+
+		pkgs, title := ParsePrefixedTitle(i.Title)
+		var labels []issues.Label
+		for _, l := range i.Labels {
+			labels = append(labels, issues.Label{
+				Name: l.Name,
+				// TODO: Can we use label ID to figure out its color?
+				Color: issues.RGB{R: 0xed, G: 0xed, B: 0xed}, // maintner.Corpus doesn't support GitHub issue label colors, so fall back to a default light gray.
+			})
+		}
+		sort.Slice(labels, func(i, j int) bool { return labels[i].Name < labels[j].Name })
+		var replies int
+		err := i.ForeachComment(func(*maintner.GitHubComment) error {
+			replies++
+			return nil
+		})
+		if err != nil {
+			panic(fmt.Errorf("internal error: ForeachComment returned non-nil error: %v", err))
+		}
+		issue := issues.Issue{
+			ID:     uint64(i.Number),
+			State:  ghState(i),
+			Title:  title,
+			Labels: labels,
+			Comment: issues.Comment{
+				User:      ghUser(i.User),
+				CreatedAt: i.Created,
+			},
+			Replies: replies,
+		}
+
+		var added bool
+		for _, p := range pkgs {
+			pi := packageIssues[p]
+			if pi == nil {
+				continue
+			}
+			switch issue.State {
+			case issues.OpenState:
+				pi.Open = append(pi.Open, issue)
+			case issues.ClosedState:
+				pi.Closed = append(pi.Closed, issue)
+			}
+			added = true
+		}
+		if !added {
+			pi := packageIssues[otherPackages]
+			issue.Title = i.Title
+			switch issue.State {
+			case issues.OpenState:
+				pi.Open = append(pi.Open, issue)
+			case issues.ClosedState:
+				pi.Closed = append(pi.Closed, issue)
+			}
+		}
+
+		return nil
+	})
+	if err != nil {
+		panic(fmt.Errorf("internal error: ForeachIssue returned non-nil error: %v", err))
+	}
+	// Sort issues by ID (newest first).
+	for _, p := range packageIssues {
+		sort.Slice(p.Open, func(i, j int) bool { return p.Open[i].ID > p.Open[j].ID })
+		sort.Slice(p.Closed, func(i, j int) bool { return p.Closed[i].ID > p.Closed[j].ID })
+	}
+	return packageIssues
+}
+
+const otherPackages = "other"
+
+// ParsePrefixedTitle parses a prefixed issue title.
+// It returns a list of paths from the prefix, and the remaining issue title.
+// It does not try to verify whether each path is an existing Go package.
+//
+// Supported forms include:
+//
+// 	"import/path: Issue title."    -> ["import/path"],       "Issue title."
+// 	"proposal: path: Issue title." -> ["path"],              "Issue title."  # Proposal.
+// 	"Proposal: path: Issue title." -> ["path"],              "Issue title."  # Proposal.
+// 	"x/path: Issue title."         -> ["golang.org/x/path"], "Issue title."  # "x/..." refers to "golang.org/x/...".
+// 	"path1, path2: Issue title."   -> ["path1", "path2"],    "Issue title."  # Multiple comma-separated paths.
+//
+// If there's no path prefix (preceded by ": "), title is returned unmodified
+// with an empty paths list:
+//
+// 	"Issue title."                 -> [], "Issue title."
+//
+func ParsePrefixedTitle(prefixedTitle string) (paths []string, title string) {
+	prefixedTitle = strings.TrimPrefix(prefixedTitle, "proposal: ") // TODO: Consider preserving the "proposal: " prefix?
+	prefixedTitle = strings.TrimPrefix(prefixedTitle, "Proposal: ")
+	idx := strings.Index(prefixedTitle, ": ")
+	if idx == -1 {
+		return nil, prefixedTitle
+	}
+	prefix, title := prefixedTitle[:idx], prefixedTitle[idx+len(": "):]
+	if strings.ContainsAny(prefix, "{}") {
+		// TODO: Parse "x/image/{tiff,bmp}" as ["x/image/tiff", "x/image/bmp"], maybe?
+		return []string{prefix}, title
+	}
+	paths = strings.Split(prefix, ",")
+	for i := range paths {
+		paths[i] = strings.TrimSpace(paths[i])
+		if strings.HasPrefix(paths[i], "x/") { // Map "x/..." to "golang.org/x/...".
+			paths[i] = "golang.org/x/" + paths[i][len("x/"):]
+		}
+	}
+	return paths, title
+}
+
+// ghState converts a GitHub issue state into a issues.State.
+func ghState(issue *maintner.GitHubIssue) issues.State {
+	switch issue.Closed {
+	case false:
+		return issues.OpenState
+	case true:
+		return issues.ClosedState
+	default:
+		panic("unreachable")
+	}
+}
+
+// ghUser converts a GitHub user into a users.User.
+func ghUser(user *maintner.GitHubUser) users.User {
+	return users.User{
+		UserSpec: users.UserSpec{
+			ID:     uint64(user.ID),
+			Domain: "github.com",
+		},
+		Login:     user.Login,
+		AvatarURL: fmt.Sprintf("https://avatars.githubusercontent.com/u/%d?v=4&s=96", user.ID),
+		HTMLURL:   fmt.Sprintf("https://github.com/%v", user.Login),
+	}
+}
service_test.go
@@ -0,0 +1,50 @@
+package main_test
+
+import (
+	"reflect"
+	"testing"
+
+	gido "dmitri.shuralyov.com/website/gido"
+)
+
+func TestParsePrefixedTitle(t *testing.T) {
+	tests := []struct {
+		in        string
+		wantPaths []string
+		wantTitle string
+	}{
+		{
+			in:        "import/path: Issue title.",
+			wantPaths: []string{"import/path"}, wantTitle: "Issue title.",
+		},
+		{ // Proposal.
+			in:        "proposal: path: Issue title.",
+			wantPaths: []string{"path"}, wantTitle: "Issue title.",
+		},
+		{ // Proposal.
+			in:        "Proposal: path: Issue title.",
+			wantPaths: []string{"path"}, wantTitle: "Issue title.",
+		},
+		{ // "x/..." refers to "golang.org/x/...".
+			in:        "x/path: Issue title.",
+			wantPaths: []string{"golang.org/x/path"}, wantTitle: "Issue title.",
+		},
+		{ // Multiple comma-separated paths.
+			in:        "path1, path2: Issue title.",
+			wantPaths: []string{"path1", "path2"}, wantTitle: "Issue title.",
+		},
+		{ // No path prefix.
+			in:        "Issue title.",
+			wantPaths: nil, wantTitle: "Issue title.",
+		},
+	}
+	for _, tc := range tests {
+		gotPaths, gotTitle := gido.ParsePrefixedTitle(tc.in)
+		if !reflect.DeepEqual(gotPaths, tc.wantPaths) {
+			t.Errorf("got: %q, want: %q", gotPaths, tc.wantPaths)
+		}
+		if gotTitle != tc.wantTitle {
+			t.Errorf("got: %q, want: %q", gotTitle, tc.wantTitle)
+		}
+	}
+}
util.go
@@ -0,0 +1,148 @@
+package main
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/shurcooL/httperror"
+	"github.com/shurcooL/users"
+)
+
+// errorHandler factors error handling out of the HTTP handler.
+type errorHandler struct {
+	handler func(w http.ResponseWriter, req *http.Request) error
+	users   interface {
+		GetAuthenticated(context.Context) (users.User, error)
+	} // May be nil if there's no users service.
+}
+
+func (h *errorHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	rw := &responseWriterHeader{ResponseWriter: w}
+	err := h.handler(rw, req)
+	if err == nil {
+		// Do nothing.
+		return
+	}
+	if err != nil && rw.WroteHeader {
+		// The header has already been written, so it's too late to send
+		// a different status code. Just log the error and move on.
+		log.Println(err)
+		return
+	}
+	if err, ok := httperror.IsMethod(err); ok {
+		httperror.HandleMethod(w, err)
+		return
+	}
+	if err, ok := httperror.IsRedirect(err); ok {
+		if req.Method == http.MethodGet { // Workaround for https://groups.google.com/forum/#!topic/golang-nuts/9AVyMP9C8Ac.
+			w.Header().Set("Content-Type", "text/html; charset=utf-8")
+		}
+		http.Redirect(w, req, err.URL, http.StatusSeeOther)
+		return
+	}
+	if err, ok := httperror.IsBadRequest(err); ok {
+		httperror.HandleBadRequest(w, err)
+		return
+	}
+	if err, ok := httperror.IsHTTP(err); ok {
+		code := err.Code
+		error := fmt.Sprintf("%d %s", code, http.StatusText(code))
+		if user, e := h.getAuthenticated(req.Context()); e == nil && user.SiteAdmin {
+			error += "\n\n" + err.Error()
+		}
+		http.Error(w, error, code)
+		return
+	}
+	if os.IsNotExist(err) {
+		log.Println(err)
+		error := "404 Not Found"
+		if user, e := h.getAuthenticated(req.Context()); e == nil && user.SiteAdmin {
+			error += "\n\n" + err.Error()
+		}
+		http.Error(w, error, http.StatusNotFound)
+		return
+	}
+	if os.IsPermission(err) {
+		log.Println(err)
+		error := "403 Forbidden"
+		if user, e := h.getAuthenticated(req.Context()); e == nil && user.SiteAdmin {
+			error += "\n\n" + err.Error()
+		}
+		http.Error(w, error, http.StatusForbidden)
+		return
+	}
+
+	log.Println(err)
+	error := "500 Internal Server Error"
+	if user, e := h.getAuthenticated(req.Context()); e == nil && user.SiteAdmin {
+		error += "\n\n" + err.Error()
+	}
+	http.Error(w, error, http.StatusInternalServerError)
+}
+
+func (h *errorHandler) getAuthenticated(ctx context.Context) (users.User, error) {
+	if h.users == nil {
+		return users.User{}, errors.New("no users service")
+	}
+	return h.users.GetAuthenticated(ctx)
+}
+
+// responseWriterHeader wraps a real http.ResponseWriter and captures
+// whether or not the header has been written.
+type responseWriterHeader struct {
+	http.ResponseWriter
+
+	WroteHeader bool // Write or WriteHeader was called.
+}
+
+func (rw *responseWriterHeader) Write(p []byte) (n int, err error) {
+	rw.WroteHeader = true
+	return rw.ResponseWriter.Write(p)
+}
+func (rw *responseWriterHeader) WriteHeader(code int) {
+	rw.WroteHeader = true
+	rw.ResponseWriter.WriteHeader(code)
+}
+
+// top adds some instrumentation on top of Handler.
+type top struct{ Handler http.Handler }
+
+func (t top) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	path := req.URL.Path
+	started := time.Now()
+	rw := &responseWriterBytes{ResponseWriter: w}
+	t.Handler.ServeHTTP(rw, req)
+	fmt.Printf("TIMING: %s: %v\n", path, time.Since(started))
+	if path != req.URL.Path {
+		log.Printf("warning: req.URL.Path was modified from %v to %v\n", path, req.URL.Path)
+	}
+	if rw.WroteBytes && !haveType(w) {
+		log.Printf("warning: Content-Type header not set for %v %q\n", req.Method, path)
+	}
+}
+
+// haveType reports whether w has the Content-Type header set.
+func haveType(w http.ResponseWriter) bool {
+	_, ok := w.Header()["Content-Type"]
+	return ok
+}
+
+// responseWriterBytes wraps a real http.ResponseWriter and captures
+// whether any bytes were written.
+type responseWriterBytes struct {
+	http.ResponseWriter
+
+	WroteBytes bool // Whether non-zero bytes have been written.
+}
+
+func (rw *responseWriterBytes) Write(p []byte) (n int, err error) {
+	if len(p) > 0 {
+		rw.WroteBytes = true
+	}
+	return rw.ResponseWriter.Write(p)
+}