Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Highlight with Prism in a worker and gzip text responses

306f36fcef89c145abcd0c5331453d0b179f37ce · Keenan Nemetz · 12d ago · parent e5542ac6

Highlight with Prism in a worker and gzip text responses

The code browser now names a blob's language and lets the browser colour it.
Chroma is gone: server/web/highlight.go is an extension table, and
static/prism-worker.js runs Prism — every language Prism ships — off the main
thread. The server sends escaped lines with their numbers and L# anchors, so a
page is complete and scrollable before any script runs and the worker only swaps
the contents of each line cell. The table covers 186 languages; a file it does
not claim stays plain rather than being coloured by a guess.

The bundle those grammars make is 545 KB, so the instance now gzips text
responses itself instead of relying on a proxy, and static assets carry a
content ETag so a browser revalidates with a 304 rather than re-downloading the
bundle on every code page. The raw-download route opts out with
Cache-Control: no-transform because it promises an exact Content-Length.

This also makes JavaScript a requirement rather than a nicety: the label and
assignee token inputs lose their noscript save buttons and their comma-separated
text fallback, and the docs say so.

Measured: release binary 26.08 MB -> 22.70 MB; /r/{repo}/code 11.1 KB -> 2.6 KB
over the wire; the Prism bundle 545 KB -> 206 KB gzipped, fetched once.

scripts/vendor-prism.py regenerates the bundle from Prism's components.json
require graph, and --check fails if the committed assets drift.

server/web/static/vole.css +41−57

@@ -18,7 +18,7 @@
18 18 repository overview summary page: main column and sidebar
19 19 code tab bar and crumbs the bar above the open file
20 20 code browser and file tree the persistent tree beside the pane
21 syntax highlighting chroma classes
21 syntax highlighting prism token classes
22 22 repository overview sidebar compact commit and run rows
23 23 run detail summary cards, step timing and lazy logs
24 24 issues and changes list pages, thread, forms, metadata sidebar
@@ -943,7 +943,6 @@ td.fit { width: 1%; white-space: nowrap; }
943 943 .code-tab-actions a.active { color: var(--accent); }
944 944 .code-tab-actions .muted { font-size: 11.5px; }
945 945 .code-tabbar + .panel,
946 .code-tabbar + .highlight,
947 946 .code-tabbar + .filebody,
948 947 .code-tabbar + .card,
949 948 .code-tabbar + .readme {
@@ -1062,7 +1061,6 @@ details[open] > summary .tree-chevron { transform: rotate(90deg); }
1062 1061 .code-main .filebody { max-width: none; }
1063 1062 .code-main .readme > *,
1064 1063 .code-main .filebody > * { max-width: none; }
1065 .code-main .highlight,
1066 1064 .code-main .blob { max-height: calc(100vh - 240px); }
1067 1065 .empty-code { margin-top: 0; }
1068 1066
@@ -1078,7 +1076,6 @@ details[open] > summary .tree-chevron { transform: rotate(90deg); }
1078 1076 @media (max-width: 760px) {
1079 1077 .code-browser { grid-template-columns: 1fr; }
1080 1078 .file-tree { position: static; max-height: 260px; }
1081 .code-main .highlight,
1082 1079 .code-main .blob { max-height: none; }
1083 1080 }
1084 1081
@@ -1127,58 +1124,45 @@ details[open] > summary .tree-chevron { transform: rotate(90deg); }
1127 1124
1128 1125 /* ---------- syntax highlighting ---------- */
1129 1126
1130 .highlight {
1131 border: 1px solid var(--border);
1132 border-radius: var(--radius-lg);
1133 background: var(--bg-panel);
1134 overflow: auto;
1135 max-height: 76vh;
1136 margin: 12px 0 20px;
1137 }
1138 .highlight pre.chroma {
1139 margin: 0;
1140 padding: 12px 0;
1141 border: 0;
1142 background: transparent;
1143 font-family: var(--mono);
1144 font-size: 12px;
1145 line-height: 19px;
1146 overflow: visible;
1147 }
1148 .chroma .line { display: block; }
1149 .chroma .ln {
1150 display: inline-block;
1151 width: 3.5rem;
1152 padding-right: 8px;
1153 margin-right: 12px;
1154 text-align: right;
1155 color: var(--fg-faint);
1156 user-select: none;
1157 border-right: 1px solid var(--border);
1158 }
1159 .chroma .cl { white-space: pre; }
1160 .chroma .w { color: inherit; }
1161
1162 .chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs,
1163 .chroma .cp, .chroma .cpf { color: #6f7682; font-style: italic; }
1164 .chroma .k, .chroma .kc, .chroma .kd, .chroma .kn, .chroma .kp,
1165 .chroma .kr, .chroma .kt { color: #c792ea; }
1166 .chroma .s, .chroma .sa, .chroma .sb, .chroma .sc, .chroma .dl,
1167 .chroma .sd, .chroma .s2, .chroma .se, .chroma .sh, .chroma .si,
1168 .chroma .sx, .chroma .sr, .chroma .s1, .chroma .ss { color: #a8d98a; }
1169 .chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi,
1170 .chroma .il, .chroma .mo { color: #f2a06b; }
1171 .chroma .nf, .chroma .fm { color: #83aaff; }
1172 .chroma .nc, .chroma .nn, .chroma .ne, .chroma .nd { color: #f0c674; }
1173 .chroma .nb, .chroma .bp { color: #7fd3e8; }
1174 .chroma .o, .chroma .ow { color: #7fd3e8; }
1175 .chroma .p, .chroma .pi { color: #a6accd; }
1176 .chroma .nt { color: #c792ea; }
1177 .chroma .na { color: #f0c674; }
1178 .chroma .nv, .chroma .vc, .chroma .vg, .chroma .vi, .chroma .vm { color: #e7e8ea; }
1179 .chroma .gd { color: var(--bad); }
1180 .chroma .gi { color: var(--good); }
1181 .chroma .err { color: var(--bad); text-decoration: underline; }
1127 /* static/prism-worker.js writes Prism token classes into the cells after the
1128 page paints. The palette is the same one the rest of the surface uses, so a
1129 token is a colour, never a font switch except for comments and italics. */
1130
1131 .blob .token.comment,
1132 .blob .token.prolog,
1133 .blob .token.doctype,
1134 .blob .token.cdata { color: #6f7682; font-style: italic; }
1135 .blob .token.punctuation { color: #a6accd; }
1136 .blob .token.namespace { color: #a6accd; }
1137 .blob .token.operator,
1138 .blob .token.entity,
1139 .blob .token.url { color: #7fd3e8; }
1140 .blob .token.boolean,
1141 .blob .token.number,
1142 .blob .token.constant,
1143 .blob .token.symbol { color: #f2a06b; }
1144 .blob .token.string,
1145 .blob .token.char,
1146 .blob .token.attr-value,
1147 .blob .token.regex { color: #a8d98a; }
1148 .blob .token.atrule,
1149 .blob .token.keyword,
1150 .blob .token.tag,
1151 .blob .token.selector { color: #c792ea; }
1152 .blob .token.function,
1153 .blob .token.function-name,
1154 .blob .token.method { color: #83aaff; }
1155 .blob .token.class-name,
1156 .blob .token.builtin,
1157 .blob .token.attr-name,
1158 .blob .token.property,
1159 .blob .token.title { color: #f0c674; }
1160 .blob .token.variable { color: #e7e8ea; }
1161 .blob .token.deleted { color: var(--bad); }
1162 .blob .token.inserted { color: var(--good); }
1163 .blob .token.important,
1164 .blob .token.bold { font-weight: 600; }
1165 .blob .token.italic { font-style: italic; }
1182 1166
1183 1167 /* ---------- repository overview sidebar ---------- */
1184 1168
@@ -1613,7 +1597,7 @@ details[open] > summary .tree-chevron { transform: rotate(90deg); }
1613 1597 .reply-target strong { color: var(--fg); }
1614 1598
1615 1599 /* An anchored comment can show the lines it was written about. The details
1616 element keeps the conversation compact; nothing here needs JavaScript. */
1600 element keeps the conversation compact. */
1617 1601 .comment-snippet {
1618 1602 margin: 0 14px 12px;
1619 1603 border: 1px solid var(--border);