From 5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 2 Aug 2026 23:43:08 -0300 Subject: 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) --- web/templates/frag-activity.tmpl | 36 +++++++++++++ web/templates/frag-change.tmpl | 41 ++++++++++++++ web/templates/frag-jj.tmpl | 33 ++++++++++++ web/templates/frag-note.tmpl | 105 ++++++++++++++++++++++++++++++++++++ web/templates/layout.tmpl | 94 ++++++++++++++++++++++++++++++++ web/templates/page-change.tmpl | 38 +++++++++++++ web/templates/page-file.tmpl | 20 +++++++ web/templates/page-files.tmpl | 13 +++++ web/templates/page-index.tmpl | 56 +++++++++++++++++++ web/templates/page-jj.tmpl | 25 +++++++++ web/templates/page-note.tmpl | 54 +++++++++++++++++++ web/templates/page-notes.tmpl | 52 ++++++++++++++++++ web/templates/page-session.tmpl | 70 ++++++++++++++++++++++++ web/templates/projects.tmpl | 113 +++++++++++++++++++++++++++++++++++++++ 14 files changed, 750 insertions(+) create mode 100644 web/templates/frag-activity.tmpl create mode 100644 web/templates/frag-change.tmpl create mode 100644 web/templates/frag-jj.tmpl create mode 100644 web/templates/frag-note.tmpl create mode 100644 web/templates/layout.tmpl create mode 100644 web/templates/page-change.tmpl create mode 100644 web/templates/page-file.tmpl create mode 100644 web/templates/page-files.tmpl create mode 100644 web/templates/page-index.tmpl create mode 100644 web/templates/page-jj.tmpl create mode 100644 web/templates/page-note.tmpl create mode 100644 web/templates/page-notes.tmpl create mode 100644 web/templates/page-session.tmpl create mode 100644 web/templates/projects.tmpl (limited to 'web/templates') diff --git a/web/templates/frag-activity.tmpl b/web/templates/frag-activity.tmpl new file mode 100644 index 0000000..071c556 --- /dev/null +++ b/web/templates/frag-activity.tmpl @@ -0,0 +1,36 @@ +{{/* Who is reading the codebase right now, straight off the log tail. */}} +{{define "activity"}} +{{if .Compact}} +
+ {{range .Sessions}} +
+ {{if and $.Live .ID}}{{.Short}} + {{else}}{{.Short}}{{end}} + {{.LastOp}} + {{.Ago}} +
+ {{end}} + {{if not .Sessions}}

nothing logged yet

{{end}} +
+{{else}} + + + + {{range .Sessions}} + + + + + + + + + + {{end}} + +
agentmodelsessionlast opwhenr/g/ndir
{{.Short}}{{if .Live}}●{{end}}{{.Model}}{{if .Reason}}/{{.Reason}}{{end}}{{if and $.Live .ID}}{{short .ID}} + {{else}}{{short .ID}}{{end}}{{.LastOp}}{{.Ago}}{{.Reads}}/{{.Greps}}/{{.Notes}}{{base .Dir}}
+ {{if not .Sessions}}

nothing logged yet

{{end}} +

live = active in the last {{.Window}}

+{{end}} +{{end}} diff --git a/web/templates/frag-change.tmpl b/web/templates/frag-change.tmpl new file mode 100644 index 0000000..b33d746 --- /dev/null +++ b/web/templates/frag-change.tmpl @@ -0,0 +1,41 @@ +{{/* The change everything on screen is read at, and how wide the view is. + Every page carries it, because every read, note and file listing below + is relative to it. */}} +{{define "changebar"}} +
+
+ change + {{if .Live}}{{short .ChangeID}} + {{else}}{{short .ChangeID}}{{end}} + {{with .ChangeInfo}}{{if .Flags}}{{.Flags}}{{end}}{{end}} +
+ {{with .ChangeInfo}} +

{{if .Desc}}{{.Desc}}{{else}}(no description){{end}}

+ {{end}} + +

+ {{if eq .Scope "all"}} + {{.Site.NotesTotal}} notes across every change{{if .Here}} · {{.Here}} here{{end}} + {{else if .ScopeEmpty}} + nothing recorded at this change — showing all {{.Site.NotesTotal}} + {{else}} + {{.Here}} notes here{{if .Elsewhere}} · {{.Elsewhere}} elsewhere{{end}} + {{end}} +

+ + {{if .Live}} + +
+ + {{if eq .Scope "all"}}{{end}} +
+ {{if .Rev}}

← back to {{short .Default}}, where the log points

{{end}} + {{end}} +
+{{end}} diff --git a/web/templates/frag-jj.tmpl b/web/templates/frag-jj.tmpl new file mode 100644 index 0000000..61040dc --- /dev/null +++ b/web/templates/frag-jj.tmpl @@ -0,0 +1,33 @@ +{{/* jj query results. Live-only, so routes are absolute. */}} +{{define "jjrows"}} +{{if .Err}}

{{.Err}}

{{end}} +{{if .Rows}} +
+ {{range .Rows}} + {{if .Blank}}
{{.Graph}}
+ {{else if .Op}} +
+ {{.Graph}} + {{.Short}} + {{.When}} + {{.Author}} + {{.Desc}} + {{.End}} +
+ {{else}} +
+ {{.Graph}} + {{.Short}} + {{.CommitShort}} + {{.When}} + {{.Author}} + {{if .Refs}}{{.Refs}}{{end}} + {{if .Flags}}{{.Flags}}{{end}} + {{if .Desc}}{{.Desc}}{{else}}(no description){{end}} +
+ {{end}} + {{end}} +
+{{else if not .Err}}

no revisions matched

{{end}} +{{if .AtOp}}

as of operation {{short .AtOp}} · back to the op log

{{end}} +{{end}} diff --git a/web/templates/frag-note.tmpl b/web/templates/frag-note.tmpl new file mode 100644 index 0000000..3b04e11 --- /dev/null +++ b/web/templates/frag-note.tmpl @@ -0,0 +1,105 @@ +{{define "note"}}{{$n := .N}}{{$p := .P}}{{$away := and $n.Change $p.ChangeID (ne $n.Change $p.ChangeID)}} +
+
+ {{if $n.File}}{{base $n.File}}{{lines $n}}{{end}} + {{if $n.Kind}}{{$n.Kind}}{{end}} + {{$n.Agent.Short}} +
+
{{md $n.Text $p}}
+ +
+{{end}} + +{{define "notelist"}}{{range .Notes}}{{template "note" (dict "N" . "P" $)}}{{end}}{{if not .Notes}}

no notes match these filters

{{end}}{{end}} + +{{/* htmx responses: the list plus an out-of-band count update */}} +{{define "notelist-oob"}}{{len .Notes}}{{template "notelist" .}}{{end}} + +{{define "note-added"}}{{if .File}}{{len .File.Notes}}{{end}}{{range .Notes}}{{template "note" (dict "N" . "P" $)}}{{end}}{{end}} + +{{/* A child-note form appears below every full card in a focused thread. It + deliberately uses a normal POST: one append and one redirect rebuild the + entire DAG from the log, including shared mention paths. */}} +{{define "replyform"}}{{$n := .N}}{{$p := .P}} +
+ reply to #{{$n.ID}} +
+ + + + + + +
+ as {{$p.User}} at {{short $p.ChangeID}} → the log + +
+
+
+{{end}} + +{{/* Recursive at arbitrary depth. A DAG join/cycle is a reference rather than + a second card, which keeps ids unique and recursion finite. */}} +{{define "threadnode"}}{{$x := .Node}}{{$p := .P}} +
+
{{if eq $x.Via "reply"}}↳{{else}}↶{{end}}
+
+ {{if $x.Reference}} +

{{$x.Via}} also reaches note #{{$x.Note.ID}}

+ {{else}} + {{template "note" (dict "N" $x.Note "P" $p)}} + {{if $p.Live}}{{template "replyform" (dict "N" $x.Note "P" $p)}}{{end}} + {{if $x.Children}}
{{range $x.Children}}{{template "threadnode" (dict "Node" . "P" $p)}}{{end}}
{{end}} + {{end}} +
+
+{{end}} + +{{/* the add-a-comment form; live pages only, since it writes to the log */}} +{{define "noteform"}} +
+ + + + +
+ {{if .File}} + + on {{base .File.Path}} + {{else}} + + {{end}} + + + +
+ +
+ as {{.User}} at {{short .ChangeID}} → the log + +
+
+{{range .Facets.Kinds}} +{{end}} diff --git a/web/templates/layout.tmpl b/web/templates/layout.tmpl new file mode 100644 index 0000000..969cae6 --- /dev/null +++ b/web/templates/layout.tmpl @@ -0,0 +1,94 @@ + + + + + +{{.Title}} + + + +{{if .Live}}{{end}} + + + + + + +
+{{if .Err}}

{{.Err}}

{{end}} +{{if eq .Kind "file"}}{{template "page-file" .}} +{{else if eq .Kind "notes"}}{{template "page-notes" .}} +{{else if eq .Kind "note"}}{{template "page-note" .}} +{{else if eq .Kind "files"}}{{template "page-files" .}} +{{else if eq .Kind "jj"}}{{template "page-jj" .}} +{{else if eq .Kind "change"}}{{template "page-change" .}} +{{else if eq .Kind "session"}}{{template "page-session" .}} +{{else}}{{template "page-index" .}}{{end}} +
+ +{{if eq .Kind "file"}} + +{{end}} + + + diff --git a/web/templates/page-change.tmpl b/web/templates/page-change.tmpl new file mode 100644 index 0000000..8641da7 --- /dev/null +++ b/web/templates/page-change.tmpl @@ -0,0 +1,38 @@ +{{define "page-change"}} +{{$c := .Change}} +{{$p := .}} +
+

{{$c.Row.Short}} {{$c.Row.Flags}}

+

+ commit {{$c.Row.CommitShort}} · {{$c.Row.Author}} · {{$c.Row.When}} + {{if $c.Row.Refs}} · {{$c.Row.Refs}}{{end}} · + browse the tree here +

+
+ +
+ {{if $c.Desc}}
{{$c.Desc}}
{{else}}

(no description set)

{{end}} + + {{if $c.Parents}} +

parents: + {{range $c.Parents}}{{.Short}} {{.Desc}} {{end}} +

+ {{end}} + +

Changed files {{$c.Stat}}

+ {{if $c.Files}} + + + {{range $c.Files}} + + + {{end}} + +
{{.Status}}{{.Path}}
+ {{else}}

no file changes

{{end}} + +

Notes recorded at this change {{len .Notes}}

+
{{range .Notes}}{{template "note" (dict "N" . "P" $)}}{{end}}
+ {{if not .Notes}}

nothing in the log was written while this change was checked out

{{end}} +
+{{end}} diff --git a/web/templates/page-file.tmpl b/web/templates/page-file.tmpl new file mode 100644 index 0000000..39a8c44 --- /dev/null +++ b/web/templates/page-file.tmpl @@ -0,0 +1,20 @@ +{{define "page-file"}} +
+

{{.File.Path}}

+

+ {{if .File.Traced}} + {{range .Site.Agents}}{{if band $.File.Mask .Bit}}{{.Short}} {{end}}{{end}} + {{.File.Pct}}% read · + {{.File.Reads}} reads · {{.File.Greps}} greps · + {{len .File.Notes}} note{{if ne (len .File.Notes) 1}}s{{end}} · + {{else}}not traced · {{end}}change + {{if .Live}}{{short .File.Change}}{{else}}{{short .File.Change}}{{end}} +

+ {{if .File.Patterns}}

grepped: {{range .File.Patterns}}{{.}} {{end}}

{{end}} +
+{{if .File.Stub}} +

{{.File.Stub}}

+{{else}} +
{{.File.Body}}
+{{end}} +{{end}} diff --git a/web/templates/page-files.tmpl b/web/templates/page-files.tmpl new file mode 100644 index 0000000..3f158fe --- /dev/null +++ b/web/templates/page-files.tmpl @@ -0,0 +1,13 @@ +{{define "page-files"}} +
+

Files

+

+ everything at change {{short .ChangeID}} · + traced files carry a coverage bar{{if eq .Scope "change"}}, from reads at this change{{end}} + ◆ marks files with notes +

+
+
+{{.Tree}} +
+{{end}} diff --git a/web/templates/page-index.tmpl b/web/templates/page-index.tmpl new file mode 100644 index 0000000..93cad6b --- /dev/null +++ b/web/templates/page-index.tmpl @@ -0,0 +1,56 @@ +{{define "page-index"}} +
+

{{.Site.Title}}

+

+ {{len .Files}} files at {{short .ChangeID}} · + {{len .Site.Order}} traced, {{len .Site.Notes}} notes + {{if or (eq .Scope "all") .ScopeEmpty}}across every change{{else}}here{{end}} · + theme {{.Site.Theme.Name}} +

+
+ +{{if .Live}} +
+

Live — agents reading this codebase

+
+

…

+
+
+{{end}} + +
+

Agents

+ + + + {{range .Site.Agents}} + + + + + {{end}} + +
agentmodeleffortsessionreadsgrepsnotes
{{.Short}}{{.Model}}{{.Reason}}{{if and $.Live .Session}}{{short .Session}} + {{else}}{{short .Session}}{{end}}{{.Reads}}{{.Greps}}{{.Notes}}
+
+ +
+

Coverage

+ + + + {{range .Site.Order}}{{with index $.Site.Files .}} + + + + {{end}}{{end}} + +
filereadlinesnotes
{{.Path}}{{.Pct}}%{{.Covered}}/{{.Total}}{{len .Notes}}
+
+ +
+

Latest notes — all {{len .Site.Notes}}, with filters

+
{{range .Notes}}{{template "note" (dict "N" . "P" $)}}{{end}}
+ {{if not .Notes}}

no notes in the log yet

{{end}} +
+{{end}} diff --git a/web/templates/page-jj.tmpl b/web/templates/page-jj.tmpl new file mode 100644 index 0000000..24df596 --- /dev/null +++ b/web/templates/page-jj.tmpl @@ -0,0 +1,25 @@ +{{define "page-jj"}} +
+

jj

+

+ read-only queries against the repo — --ignore-working-copy, inline templates, nothing written to disk +

+
+ +
+
+ + + + + + · +
+

+ change ids open the change · descriptions browse the tree at that change · + op ids re-run the query --at-operation, which is how you see the repo as it was +

+
{{template "jjrows" .JJ}}
+
+{{end}} diff --git a/web/templates/page-note.tmpl b/web/templates/page-note.tmpl new file mode 100644 index 0000000..8cd0e64 --- /dev/null +++ b/web/templates/page-note.tmpl @@ -0,0 +1,54 @@ +{{/* One note, on its own — the page a shared permalink opens. + + It has to stand up for a reader who has never seen this site: what was + said, about which lines of which file, at which change, and the code + itself, right there. Everything else on the page is a way further in. */}} +{{define "page-note"}} +{{$n := .Note}} +
+

{{if $n.File}}{{base $n.File}}{{lines $n}}{{else}}Note{{end}}

+

+ note #{{$n.ID}} by {{$n.Agent.Short}}{{if $n.Kind}} · {{$n.Kind}}{{end}} + {{if not $n.At.IsZero}} · + ({{$n.Ago}}){{end}} + {{if $n.Change}} · read at + {{if .Live}}{{short $n.Change}} + {{else}}{{short $n.Change}}{{end}} + {{end}} +

+ {{if ne $n.File (base $n.File)}}

{{$n.File}}

{{end}} +
+ +
+
{{template "note" (dict "N" $n "P" $)}}
+ + {{$open := .HasCode $n.File}} + {{if .Excerpt}} +

lines {{.ExFrom}}–{{.ExTo}} of {{$n.File}}{{if $open}} · + open the whole file →{{end}}

+
{{.Excerpt}}
+ {{else if .ExErr}} +

{{.ExErr}}

+ {{else if $n.File}} +

this note is about the whole file{{if $open}} — + open {{base $n.File}} →{{end}}

+ {{else}} +

this note is not pinned to a file

+ {{end}} + + {{if .Live}}{{template "replyform" (dict "N" $n "P" $)}}{{end}} + +
+

Replies & mentions + {{len $n.Replies}} repl{{if eq (len $n.Replies) 1}}y{{else}}ies{{end}} · {{len $n.Mentions}} mention{{if ne (len $n.Mentions) 1}}s{{end}} +

+ {{if .Thread.Children}} +
{{range .Thread.Children}}{{template "threadnode" (dict "Node" . "P" $)}}{{end}}
+ {{else}} +

no notes reply to or mention this one yet

+ {{end}} +
+ +

← every note in the log

+
+{{end}} diff --git a/web/templates/page-notes.tmpl b/web/templates/page-notes.tmpl new file mode 100644 index 0000000..ebd5865 --- /dev/null +++ b/web/templates/page-notes.tmpl @@ -0,0 +1,52 @@ +{{define "facet"}}{{end}} + +{{define "page-notes"}} +
+

Notes

+

+ {{if eq .Scope "all"}} + all {{.Site.NotesTotal}} in the log, every change + {{else if .ScopeEmpty}} + all {{.Site.NotesTotal}} in the log — nothing was recorded at {{short .ChangeID}} + {{else}} + {{len .Site.Notes}} written at {{short .ChangeID}}{{if .Elsewhere}} · + {{if .Live}}{{.Elsewhere}} more at other changes + {{else}}{{.Elsewhere}} more at other changes are not in this export{{end}}{{end}} + {{end}} + {{if .Live}} · anything you add here is appended to the private log sidecar at this change — the project working copy is never touched{{end}} +

+
+ +
+ {{if .Live}} +
+ add a note + {{template "noteform" .}} +
+ {{end}} + +
+ + + {{template "facet" (dict "Name" "agent" "Label" "any agent" "Values" .Facets.Agents "Sel" .Filter.Agent)}} + {{template "facet" (dict "Name" "model" "Label" "any model" "Values" .Facets.Models "Sel" .Filter.Model)}} + {{template "facet" (dict "Name" "session" "Label" "any session" "Values" .Facets.Sessions "Sel" .Filter.Session "Short" true)}} + {{template "facet" (dict "Name" "kind" "Label" "any kind" "Values" .Facets.Kinds "Sel" .Filter.Kind)}} + + + + + + +
+ {{range .Facets.Files}} + +

·{{len .Notes}} notes shown

+
{{template "notelist" .}}
+
+{{end}} diff --git a/web/templates/page-session.tmpl b/web/templates/page-session.tmpl new file mode 100644 index 0000000..1155b67 --- /dev/null +++ b/web/templates/page-session.tmpl @@ -0,0 +1,70 @@ +{{/* One session's conversation, with its notevi log woven in by time. */}} +{{define "page-session"}} +{{$c := .Conv}} +
+

+ {{if $c.Agent}}{{$c.Agent}} {{end}} + {{short $c.Session}} +

+

+ {{if $c.Model}}{{$c.Model}} · {{end}}{{$c.Reads}} reads · {{$c.Greps}} greps · {{$c.Notes}} notes + {{if $c.Dir}} · {{base $c.Dir}}{{end}} + {{if $c.Harness}} · {{$c.Harness}}{{end}} +

+ {{if $c.Path}} +

{{$c.Path}}

+ {{else}} +

no transcript found for this session — searched + {{if $c.Roots}}{{range $i, $r := $c.Roots}}{{if $i}}, {{end}}{{$r}}{{end}}{{else}}nowhere: -transcripts is empty{{end}} · + showing the notevi log alone

+ {{end}} + {{if $c.Err}}

{{$c.Err}}

{{end}} +
+ +
+ {{if $c.Windowed}} +

+ moments {{$c.From}}–{{$c.To}} of {{$c.Total}}{{if $c.Anchor}}, around the note{{end}} · + {{if $c.Incomplete}}the rest is beyond what one page renders{{else}}show the whole conversation{{end}} +

+ {{end}} + +
+ {{range $c.Turns}} + {{if .Note}} +
+ note + {{if .Note.File}} +
{{template "note" (dict "N" .Note "P" $)}}
+ {{else}} +
{{template "note" (dict "N" .Note "P" $)}}
+ {{end}} +
+ {{else if eq .Role "notevi"}} +
+ notevi + {{if .File}}{{.Text}} + {{else}}{{.Text}}{{end}} +
+ {{else if eq .Role "tool"}} +
+ {{.Tool}} + {{.Detail}} +
+ {{else if eq .Role "result"}} +
+ →{{if .Tool}} {{.Tool}}{{end}} + {{.Detail}} +
+ {{else}} +
+ {{.Role}} +

{{.Text}}

+
+ {{end}} + {{end}} + {{if not $c.Turns}}

nothing recorded for this session

{{end}} +
+
+{{end}} diff --git a/web/templates/projects.tmpl b/web/templates/projects.tmpl new file mode 100644 index 0000000..ce75454 --- /dev/null +++ b/web/templates/projects.tmpl @@ -0,0 +1,113 @@ +{{define "projects"}} + + + + +{{.Title}} + + + + + + + + +
+
+

Projects

+

Choose a notevi log, start one in a jj repository, or colocate jj with an existing Git repository.

+
+ {{if .Message}}

{{.Message}}

{{end}} + {{if .Err}}

{{.Err}}

{{end}} + +
+

Logs — {{len .Active}} repositories with a private sidecar

+
+ {{range .Active}} +
+

{{.Name}}

log
+

{{.Relative}}

+

{{.Entries}} entries · {{.Notes}} notes · {{.Agents}} agents · {{.Updated}}

+ {{if gt (len .Workspaces) 1}}

{{len .Workspaces}} jj workspaces share this log

{{end}} + {{if .Err}}

{{.Err}}

{{end}} + open project +
+ {{end}} + {{if not .Active}}

no notevi sidecars found yet

{{end}} +
+
+ +
+

Ready to start — {{len .Ready}} jj repositories without a notevi sidecar

+
+ {{range .Ready}} +
+

{{.Name}}

jj
+

{{.Relative}}

+ {{if gt (len .Workspaces) 1}}

{{len .Workspaces}} workspaces

{{end}} + {{if .Err}}

{{.Err}}

{{end}} +
+ +
+
+ {{end}} + {{if not .Ready}}

every detected jj repository already has a sidecar

{{end}} +
+
+ +
+

Git repositories — {{len .Git}} without jj metadata

+

Initializing adds colocated .jj metadata; it does not create a sidecar until you choose “start private sidecar”.

+
+ {{range .Git}} +
+

{{.Name}}

git
+

{{.Relative}}

+
+ +
+
+ {{end}} + {{if not .Git}}

no Git-only repositories detected

{{end}} +
+
+
+ +{{end}} -- cgit v1.3