\n')
def repotabs(active, extra_right='jump to file t'):
tabs = ['log', 'files', 'threads 3', 'runs', 'config']
out = ('
\n')
for t in tabs:
if t.split()[0] == active:
out += (f' {t}\n')
else:
out += f' {t}\n'
out += f' {extra_right}\n
\n'
return out
def lnk(t):
return f'{t}'
def row(inner, muted=False, tall=False):
c = ' color:var(--text-muted);' if muted else ''
pad = '13px 18px' if tall else '12px 18px'
return f'
\n{inner}\n
\n'
def between(a, b):
return (f'
'
f'{a}{b}
')
def sub(t):
return f'
{t}
'
def box(t, mono_muted=True):
c = 'var(--text-secondary)' if mono_muted else 'var(--text-primary)'
return (f'
{t}
')
def field(label, value, hint=''):
h = f'\n
{hint}
' if hint else ''
return (f'
\n'
f'
{label}
\n'
f'
{value}
{h}\n
')
def btn(t):
return (f'
{t}
')
def hunk_start(header):
"""Reads the new-side start line out of an @@ header."""
import re
m = re.search(r'\+(\d+)', header)
return int(m.group(1)) if m else 1
def diff(header, lines):
"""A diff box. Lines carry their number, because 35.3 says to press it."""
out = ('
\n'
f'
{header}
\n')
n = hunk_start(header)
num = ('display:inline-block; width:30px; margin-right:9px; text-align:right; '
'color:var(--text-muted); text-decoration:underline;')
for kind, txt in lines:
if kind == '+':
st = 'background:var(--bg-success); color:var(--text-success);'
gutter = f'{n}'
txt = gutter + '+ ' + txt
n += 1
elif kind == '-':
st = 'background:var(--bg-danger); color:var(--text-danger);'
plain = num.replace('text-decoration:underline;', '')
gutter = f''
txt = gutter + '- ' + txt
elif kind == 'note':
st = 'color:var(--text-muted); text-align:center;'
else:
st = 'color:var(--text-secondary);'
gutter = f'{n}'
txt = gutter + ' ' + txt
n += 1
out += f'
{txt}
\n'
return out + '
\n'
PAGES = {}
# ---------------------------------------------------------------- identity
PAGES['signin.html'] = dict(
title=' · sign in', fl='barerepo', fr='barerepo 0.1.0',
sr='Sign in page where the server challenges an ssh key instead of asking for a password.',
body=('
barerepo'
'new account
\n'
'
\n'
'
sign in
\n'
+ field('name', 'john|') +
'
' + btn('send challenge') + '
\n'
'
\n'
'
we give you a nonce. you sign it with the key you already have.
or br auth john, '
'which does the same thing in one step.
\n'
'
\n
\n'))
PAGES['keys.html'] = dict(
title=' · keys', fl='john / keys', fr='barerepo 0.1.0',
sr='Page listing ssh keys, runner tokens and feed tokens, each with a revoke control.',
body=(topbar() +
crumb('john / keys',
'the only state the server owns') +
row('
ssh keys
') +
row(between('ed25519 SHA256:8fK2q0mR4vXeN1pLzT9wBcJdSgYo3Ea7kVnQxMuP2r', 'added mar 2026') +
sub('laptop · last used 2h · revoke')) +
row(between('ed25519 SHA256:Qw3rTy7uIoP0aSdFgHjKlZxCvBnM4eR8tYu1IoP2aSd', 'added apr 2026') +
sub('uproar · last used 4d · revoke')) +
row('
runner tokens
') +
row(between('uproar.local', 'created 3d') +
sub('john/johnbot · labels build, test · last seen 40m · '
'revoke')) +
row('
feed tokens
') +
row(between('inbox', 'created 3d') +
sub('read only · last read 20m · '
'revoke')) +
row(btn('new key') + ' ' + btn('new runner token') +
' ' + btn('new feed token')) +
row(sub('a token is shown once, inside the command that uses it. only its hash is kept, '
'so it cannot be shown again. lost one? revoke it and make another.')) +
row(sub('a key signs you in and pushes. a runner token attaches one machine to one '
'repository. a feed token reads one feed and can write nothing.')) +
row(sub('everything else is in .barerepo/config, in the repository it belongs to.'))))
# ---------------------------------------------------------------- repo start
PAGES['new-repo.html'] = dict(
title=' · new repo', fl='barerepo', fr='barerepo 0.1.0',
sr='Form for creating a new repository with a choice of default branch name.',
body=(topbar() +
'
' +
sub('merged · tip reachable from master'), muted=True)))
PAGES['thread-new.html'] = dict(
title=' · new thread', fl='john / johnbot', fr='barerepo 0.1.0',
sr='New thread form with an optional field for attaching a pushed proposal ref.',
body=(crumb('john / johnbot / new thread',
'refs/notes/threads') +
'
\n'
+ field('title', 'panic when config file is empty|') +
'
\n'
'
body
\n'
'
markdown|
\n
\n'
+ field('attach a ref', 'optional',
'push first, then paste the ref') +
'
' + btn('open') + '
\n
\n'))
# ---------------------------------------------------------------- runners
PAGES['runner-setup.html'] = dict(
title=' · add a runner', fl='john / johnbot / runs', fr='barerepo 0.1.0',
sr='Runner setup page showing one paste-ready command per operating system with the token already embedded.',
body=(crumb('john / johnbot / add a runner',
'rt_live_7Kq2mXe') +
'
\n' +
row(sub('the same message was printed in your terminal.'))))
PAGES['404.html'] = dict(
title=' · not found', fl='barerepo', fr='barerepo 0.1.0',
sr='Not found page.',
body=(topbar() +
'
\n'
'
404
\n'
'
'
'john/johnbot exists. that path in it does not.
\n
\n'))
# ---------------------------------------------------------------- index
def build_index():
groups = [
('identity', [
('signup.html', 'signup', 'ssh key is the only credential'),
('signin.html', 'sign in', 'server challenges the key'),
('keys.html', 'keys and tokens', 'the only state the server owns'),
]),
('repo, first contact', [
('new-repo.html', 'new repo', 'default branch is master'),
('repo-empty.html', 'empty repo', 'one block to paste'),
]),
('browsing', [
('repo-log.html', 'repo log', 'diffs inline, this is the landing page'),
('repo-files.html', 'file tree', 'secondary, not the front door'),
('repo-file.html', 'file view', 'blame in the gutter, always on'),
('repo-commit.html', 'commit', 'one commit, full diff'),
('repo-compare.html', 'compare', 'any ref against any ref'),
]),
('threads', [
('threads.html', 'thread list', 'issues and proposals in one list'),
('thread.html', 'thread', 'discussion stored in git notes'),
('thread-new.html', 'new thread', 'attach a ref to make it a proposal'),
]),
('runners', [
('runner-setup.html', 'add a runner', 'one line, token already in it'),
('runners.html', 'runners', 'your machines'),
('runs.html', 'runs', 'triggered by push'),
('run.html', 'run detail', 'raw log, no step theater'),
]),
('activity', [
('inbox.html', 'inbox', 'chronological, no read state, atom'),
('releases.html', 'releases', 'tag, notes, attached files'),
]),
('everything else', [
('search.html', 'search', 'code, threads, repos in one result set'),
('profile.html', 'profile', 'repo list'),
('repo-config.html', 'repo config', 'settings as a versioned file'),
('push-rejected.html', 'push rejected', 'says what the hook refused'),
('404.html', '404', ''),
]),
]
body = ('
\n')
return dict(title=' · mockups', fl='24 views', fr='barerepo 0.1.0',
sr='Index of every mockup page in this set.', body=body)
PAGES['inbox.html'] = dict(
title=' · inbox', fl='inbox', fr='barerepo 0.1.0',
sr='Chronological list of events on repositories and threads the user participates in.',
body=(topbar() +
crumb('inbox',
lnk('atom') + ' · ' + lnk('feed token')) +
row(between('lisa opened proposal 47 on johnbot', '2h') +
sub('panic when config file is empty')) +
row(between('john replied on johnbot thread 47', '1h') +
sub('Default() allocates every call. make it a package var?')) +
row(between('build failed on johnbot refs/proposals/46', '1d') +
sub('uproar.local · exit 2')) +
'
last visited 2d ago
\n' +
row(between('mark opened thread 44 on johnbot', '5d') +
sub('does this work behind a socks proxy?'), muted=True) +
row(between('dave merged proposal 45 on johnbot', '3d') +
sub('bump deps'), muted=True) +
row(sub('events older than 90 days are dropped'))))
PAGES['releases.html'] = dict(
title=' · releases', fl='john / johnbot', fr='barerepo 0.1.0',
sr='List of tagged releases with notes and attached files.',
body=(crumb('john / johnbot / releases',
'refs/notes/releases') +
repotabs('log', 'newest first') +
row(between('v1.2.0', 'john · 3d') +
sub('fixes the empty config panic. thanks lisa.') +
'
') +
row(sub('release notes clone with the repository. attached files do not.'))))
# ---------------------------------------------------------------- folded in
# These five pages were hand-written before this generator existed and drifted
# away from it. They are here now, so `python3 build.py` really does write
# every page from one shared chrome, which is what the README claims.
PAGES['signup.html'] = dict(
title=' · signup', fl='barerepo', fr='barerepo 0.1.0',
sr='Signup page where an ssh public key is the only credential collected.',
body=('
your key is your account. there is no email and no password.
\n'
'
losing every key loses the account, '
'because there is no out-of-band recovery channel.
\n'
'
add a second key from another machine today.
\n'
'
\n
\n'))
PAGES['repo-log.html'] = dict(
title=' · johnbot', fl=lnk('older'), fr='barerepo 0.1.0',
sr='Repository log with every commit diff expanded inline. This is the landing page.',
body=(crumb('john / johnbot',
'master · clone git@barerepo:john/johnbot') +
repotabs('log') +
row(between('a3f9c2 fix panic when config is empty', 'lisa · 2h') +
sub('config.go · +4 -1') +
diff('@@ -41,7 +41,10 @@ func Load', [
(' ', 'f, err := os.Open(path)'),
('-', 'return cfg'),
('+', 'if len(raw) == 0 {'),
('+', ' return Default(), nil'),
('+', '}'),
('+', 'return cfg, nil'),
(' ', '}')]), tall=True) +
row(between('8b1d44 drop the retry loop, it never fired', 'john · 6h') +
sub('irc/conn.go · +0 -23') +
diff('@@ -88,23 +88,0 @@ func (c *Conn) dial', [
('-', 'for i := 0; i < maxRetry; i++ {'),
('-', ' time.Sleep(backoff(i))'),
('note', '18 more removed lines')]), tall=True) +
row(between('dave merged proposal 45, bump deps', '14 files · +302 -288') +
sub('3d · large diff collapsed · ' + lnk('expand')), tall=True)))
PAGES['repo-config.html'] = dict(
title=' · .barerepo/config', fl=lnk('history') + ' · ' + lnk('blame') + ' · ' + lnk('raw'),
fr='barerepo 0.1.0',
sr='Repository settings shown as a versioned file in the repository rather than a settings form.',
body=(crumb('john / johnbot / .barerepo/config',
'edited 3d by john · a3f9c2') +
repotabs('config') +
'
\n'
'
[repo]
\n'
'
default_branch = "master"
\n'
'
visibility = "public"
\n'
'
archived = false
\n'
' \n'
'
[proposals]
\n'
'
accept_from = "anyone"
\n'
'
require_runs = ["build", "test"]
\n'
' \n'
'
[access]
\n'
'
push = '
'["john", "lisa"]
\n'
'
allow_force_push = []
\n'
' \n'
'
[runners]
\n'
'
"uproar.local" = ["build", "test"]
\n'
'
\n' +
row(sub('edit the file, commit, push. there is no settings form. '
'git log -p .barerepo/config '
'shows who changed what.')) +
row(sub('raw file content is served from this host as a download. private files have no raw '
'link until the operator sets a separate raw host.'))))
PAGES['thread.html'] = dict(
title=' · thread 47', fl='threads · 3 open', fr='barerepo 0.1.0',
sr='Discussion thread where issues and proposals are one object, stored in git notes.',
body=(crumb('john / johnbot / thread 47',
'refs/notes/threads/47') +
row('
panic when config file is empty
' +
sub('opened by lisa · 2h · has proposal · '
'refs/proposals/47 · +81 -12'),
tall=True) +
row(sub('lisa · 2h') +
'
fresh install, empty config.toml, immediate nil deref on line 44. '
'reproduces every time.
\n'
for nm, when, desc, meta in [
(lnk('johnbot'), '2h', 'irc bot that refuses to leave',
'go · 4.1mb · master · 3 proposals'),
(lnk('dotfiles'), '1d', 'nvim, zsh, too many aliases', 'shell · 210kb · master'),
('uproar private',
'4d', 'server configs, do not read', 'nix · 88kb · main'),
(lnk('sqlite-notes'), '3w', 'append-only notes over sqlite',
'c · 1.2mb · master · archived'),
]) +
'
showing 4 of 14 · '
+ lnk('all') + '
\n
\n
\n'))
PAGES['index.html'] = build_index()
for fname, p in PAGES.items():
with open(os.path.join(OUT, fname), 'w', encoding='utf-8') as f:
f.write(SHELL.format(**p))
print(fname)