diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-02 23:43:08 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-03 09:54:39 -0300 |
| commit | 5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e (patch) | |
| tree | f12f4b700fff4ff6f7e818d2a542672ba0b8d76e /vrsite/main.go | |
| parent | a9263daee9413c5eff3c1f1bebcd224442fd8685 (diff) | |
| download | notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.tar.gz notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.zip | |
rebrand to notevi: one CLI over the jj sidecar
vr and vrsite become a single binary. vrsite/ folds into a web package in
one module (0x4200.cafe/notevi); "notevi web" serves and exports exactly
what vrsite did, and "notevi read/grep/note/query" is unchanged.
The sidecar is renamed with it: notevi_log, notevi-log.jsonl, and the
description "private: notevi log". The pre-rebrand names are still
recognized, so an old repository opens and reads; it is renamed in place
on the first write, or up front with "notevi migrate DIR...".
That rename cannot be a single mv inside jj run. jj only auto-tracks a
*new* file in the run working copy below a size limit it does not take
from the command line, so writing a whole log under a name the change has
never held is silently dropped while jj reports success. ensureLogFile
creates the file empty first and lets every later byte be a modification
of a tracked file, which snapshots at any size; that also fixes the same
latent bug when importing a large legacy vr-log.jsonl.
Adds a bem-te-vi mark (favicon and nav brand) and a README.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'vrsite/main.go')
| -rw-r--r-- | vrsite/main.go | 237 |
1 files changed, 0 insertions, 237 deletions
diff --git a/vrsite/main.go b/vrsite/main.go deleted file mode 100644 index e43a8eb..0000000 --- a/vrsite/main.go +++ /dev/null @@ -1,237 +0,0 @@ -// vrsite — the reading room for vr trace logs. -// -// A vr log records who read what, where, and what they thought about it. This -// program projects that log onto the jj change the code was read at: the repo -// tree with per-agent coverage colors, tree-sitter highlighted sources with -// coverage-tinted line numbers (colors mix where agents overlap), and notes — -// both the ones agents left and the ones you write in the browser. -// -// It runs as a live web app (default) or writes a static export (-out). -// Either way the log is the only state: notes are appended to a private jj -// sidecar, never to the project working copy. -// -// Theme comes from a Zed theme JSON; per-agent colors come from the theme's -// player (collaborator) palette. Every other theme this machine has — Zed's -// and Helix's both — is offered in the picker, previewed as you move through -// it, and remembered in the browser. -package main - -import ( - "embed" - "flag" - "fmt" - "os" - "os/user" - "path/filepath" - "strings" - "time" - - "0x4200.cafe/vr/sidecar" -) - -//go:embed templates assets -var tfs embed.FS - -func fatal(format string, a ...any) { - fmt.Fprintf(os.Stderr, "vrsite: "+format+"\n", a...) - os.Exit(1) -} - -const usageText = `vrsite — the reading room for vr trace logs. - -vr logs every file a coding agent reads, greps and notes, one JSON line each, -keyed to the jj change the code was read at. vrsite serves that log: the repo -tree with per-agent read coverage, syntax-highlighted sources, the notes agents -left, the conversation each note was written in, and jj queries over the repo. - -usage: - vrsite [-scan-root DIR] [-addr HOST:PORT] [-user NAME] - serve every discovered project from one dashboard; scans ~ by default - vrsite -repo DIR [-addr HOST:PORT] [-user NAME] - serve one repository directly, preserving the original single-project mode - vrsite [-repo DIR] -out DIR [-watch] - write a static copy instead — no server, so no comments, no live - activity, no jj queries; the focus toggle, filters and file finder - still work, and it holds the one change the log points at - -starting on a new project: - 1. open the dashboard; it discovers jj and Git repositories below ~ - 2. initialize colocated jj or start the private sidecar from its explicit - button — no project registry or scan result is written to disk - 3. give the agents vr and tell them to read with it; constrain/AGENTS.md in - the vr repo is a drop-in brief that does, and constrain/ can enforce it - 4. let them work, then refresh the dashboard when a new repo appears - -A log belongs to one repository. Entries carry the jj change id they were -recorded at, and vrsite reads the code at those changes, so a log from another -repo has nothing to show here. Point -repo at the repository the agents ran in. - -Notes written in the browser are appended to the same private jj sidecar, -under -user, at the exact change and commit being read. The project working -copy is never changed, and the app keeps no state of its own. - -The sidecar has no bookmark and is local-only. It is not included in a fresh -clone, so back up the jj repository or export the site when the trace matters. - -Session transcripts (the conversation behind a note) are looked up by session -id under -transcripts; Claude Code and Codex are found automatically. - -The site is built with the two themes -theme and -light-theme name. Every -other theme installed on this machine — Zed families and Helix themes both — -is offered in the picker at the bottom of the tree: it previews as you move -through it, and the browser remembers what you settled on. - -flags: -` - -func main() { - home, err := os.UserHomeDir() - if err != nil { - home = "." - } - repo := flag.String("repo", "", "serve one jj repo directly (with -out, default: current directory)") - scanRoot := flag.String("scan-root", home, "live dashboard root to scan for jj and Git repositories") - addr := flag.String("addr", "127.0.0.1:4200", "address to serve on") - who := flag.String("user", "", "name your browser notes are logged under (default: $USER)") - out := flag.String("out", "", "write a static site here instead of serving") - themePath := flag.String("theme", defaultTheme("dark"), "zed theme json for the dark scheme (default: discovered, else embedded One Dark)") - themeName := flag.String("theme-name", "", "theme name inside the family") - lightPath := flag.String("light-theme", defaultTheme("light"), "zed theme json for the light scheme (default: discovered, else embedded One Light)") - lightName := flag.String("light-theme-name", "", "theme name inside the light family") - title := flag.String("title", "vr trace", "site title") - roots := flag.String("transcripts", strings.Join(defaultTranscriptRoots(), ","), - "comma-separated roots to look for harness session transcripts in") - watch := flag.Bool("watch", false, "with -out: watch the trace log and rebuild after changes settle") - debounce := flag.Duration("debounce", 10*time.Minute, "quiet period before a watched rebuild") - flag.Usage = func() { - fmt.Fprint(flag.CommandLine.Output(), usageText) - flag.PrintDefaults() - } - flag.Parse() - if *debounce < 0 { - fatal("debounce must not be negative") - } - theme := loadTheme(*themePath, *themeName, "dark") - light := loadTheme(*lightPath, *lightName, "light") - themes := newThemeSet(theme, light) - if *out == "" && *watch { - fatal("-watch is for -out builds; the served app already follows the log") - } - - // Explicit -repo and every static export retain the original one-repository - // path. A bare live invocation is the new, stateless project dashboard. - if *repo == "" && *out == "" { - hubTitle := *title - if hubTitle == "vr trace" { - hubTitle = "vr projects" - } - hub, err := newProjectHub(*scanRoot, hubTitle, username(*who), strings.Split(*roots, ","), theme, light, themes) - if err != nil { - fatal("discover projects: %v", err) - } - serveProjects(*addr, hub) - return - } - - repoPath := *repo - if repoPath == "" { - repoPath = "." - } - repoAbs, err := filepath.Abs(repoPath) - if err != nil { - fatal("%v", err) - } - log, err := sidecar.Open(repoAbs) - if err != nil { - fatal("open jj sidecar: %v", err) - } - if *out == "" { - serve(*addr, log, repoAbs, *title, username(*who), strings.Split(*roots, ","), theme, light, themes) - return - } - build := func() { buildSite(log, repoAbs, *out, *title, theme, light, themes) } - build() - if !*watch { - return - } - fmt.Printf("vrsite: watching %s (rebuild after %s without changes)\n", log, *debounce) - watchLog(log, *debounce, build) -} - -func username(flagValue string) string { - if flagValue != "" { - return flagValue - } - if u := os.Getenv("USER"); u != "" { - return u - } - if u, err := user.Current(); err == nil && u.Username != "" { - return u.Username - } - return "web" -} - -// ── watching the log (static export only) ───────────────────────────────── - -func watchLog(log logStore, debounce time.Duration, rebuild func()) { - watchLogUntil(log, debounce, time.Second, nil, rebuild) -} - -func watchLogUntil(log logStore, debounce, poll time.Duration, done <-chan struct{}, rebuild func()) { - state, err := log.Generation() - if err != nil { - fatal("watch %s: %v", log, err) - } - - ticker := time.NewTicker(poll) - defer ticker.Stop() - var timer *time.Timer - var timerC <-chan time.Time - defer func() { - if timer != nil { - timer.Stop() - } - }() - lastStatErr := "" - - for { - select { - case <-done: - return - - case <-ticker.C: - next, err := log.Generation() - if err != nil { - if msg := err.Error(); msg != lastStatErr { - fmt.Fprintf(os.Stderr, "vrsite: watch %s: %v\n", log, err) - lastStatErr = msg - } - continue - } - lastStatErr = "" - if next == state { - continue - } - state = next - if timer == nil { - timer = time.NewTimer(debounce) - timerC = timer.C - } else { - if !timer.Stop() { - select { - case <-timer.C: - default: - } - } - timer.Reset(debounce) - } - fmt.Printf("vrsite: change detected; rebuild scheduled after %s without changes\n", debounce) - - case <-timerC: - fmt.Println("vrsite: rebuilding after trace changes settled") - timer = nil - timerC = nil - rebuild() - } - } -} |
