fix(shares): say when "the latest version" was (BEA-31) (#76)

A /s/<token> page promises the reader it always shows the latest
version, then never says when latest was — so a stranger can't tell
whether a living wiki page is from today or last March.

Print FileInfo.Time, already in hand at the point handleShared renders,
as a muted line above the content: human date, precise RFC3339 in the
title attribute. Zero time prints nothing rather than a 1970 date.

Markdown shells only. The .html/.htm branch stays a raw io.Copy and
binaries stay untouched — the new test asserts byte equality for both.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow W. Lee (Sungwon)
2026-07-29 17:09:20 +09:00
committed by GitHub
co-authored by Claude Opus 5
parent 6da3c7957e
commit ecc8328512
2 changed files with 85 additions and 3 deletions
+17 -3
View File
@@ -370,7 +370,7 @@ func (s *Server) handleShared(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, sharedMarkdownShell, html.EscapeString(path.Base(sh.Path)), body)
fmt.Fprintf(w, sharedMarkdownShell, html.EscapeString(path.Base(sh.Path)), updatedStamp(fi.Time), body)
case ".html", ".htm":
w.Header().Set("Content-Type", "text/html; charset=utf-8")
io.Copy(w, rc)
@@ -381,7 +381,20 @@ func (s *Server) handleShared(w http.ResponseWriter, r *http.Request) {
}
}
// updatedStamp renders the "how old is this?" line a share page owes its
// reader — the link promises the latest version, so it has to say when latest
// was. Zero time (a source that doesn't know) prints nothing rather than 1970.
func updatedStamp(t time.Time) string {
if t.IsZero() {
return ""
}
t = t.UTC()
return fmt.Sprintf(`<div class="updated" title="%s">Last updated %s</div>`,
html.EscapeString(t.Format(time.RFC3339)), html.EscapeString(t.Format("2 Jan 2006")))
}
// sharedMarkdownShell wraps rendered markdown in a minimal readable page.
// Verbs, in order: title, updated stamp, body.
const sharedMarkdownShell = `<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"><title>%s</title>
<style>
@@ -405,7 +418,8 @@ table.frontmatter code{white-space:pre-wrap}
pre{max-width:100%%}
footer.bdrive{margin-top:64px;padding-top:14px;border-top:1px solid #d0d7de;font-size:12.5px;color:#57606a}
footer.bdrive a{color:inherit}
@media (prefers-color-scheme: dark){footer.bdrive{border-color:#3a3a44;color:#888}}
</style></head><body>%s
.updated{font-size:12.5px;color:#57606a;margin-bottom:28px}
@media (prefers-color-scheme: dark){footer.bdrive{border-color:#3a3a44;color:#888}.updated{color:#888}}
</style></head><body>%s%s
<footer class="bdrive">Shared with <a href="https://github.com/runbear-io/beardrive" rel="noopener">BearDrive</a> — synced files for AI agent teams</footer>
</body></html>`
+68
View File
@@ -3,6 +3,7 @@ package webapp
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
@@ -365,6 +366,73 @@ func TestShareListAPIsAreStable(t *testing.T) {
// helpers
// TestShareLastUpdatedStamp: a share page promises the latest version, so a
// markdown page says when latest was — and nothing else gets injected into.
func TestShareLastUpdatedStamp(t *testing.T) {
srv, p, _, f, h := shareHub(t)
const rawHTML = "<h1>Q3</h1><script>alert(1)</script>"
const pdf = "%PDF-1.4 fake\n"
f.put("dev1", "wiki/deck.pdf", pdf)
// markdown: the stamp is the FILE's time, human date + precise title=
when := time.Date(2026, 3, 14, 9, 26, 53, 0, time.UTC)
f.putAt("dev1", "wiki/notes.md", "# Notes\n\nhello **team**", when)
token, _ := authedShare(t, srv, h, p.ID, "wiki/notes.md")
rec := do(t, h, "GET", "/s/"+token, nil)
body := rec.Body.String()
if !strings.Contains(body, "Last updated 14 Mar 2026") {
t.Fatalf("no last-updated stamp: %s", body)
}
if !strings.Contains(body, `title="2026-03-14T09:26:53Z"`) {
t.Fatalf("no precise timestamp: %s", body)
}
// ...and it sits above the content, not after it
if strings.Index(body, "Last updated") > strings.Index(body, "<strong>team</strong>") {
t.Fatal("stamp must render before the content")
}
// the sandbox + footer survive
if csp := rec.Header().Get("Content-Security-Policy"); csp != "sandbox allow-scripts allow-popups" {
t.Fatalf("CSP = %q", csp)
}
if rec.Header().Get("X-Content-Type-Options") != "nosniff" || rec.Header().Get("Referrer-Policy") != "no-referrer" {
t.Fatalf("share headers = %v", rec.Header())
}
if !strings.Contains(body, "Shared with <a") {
t.Fatal("footer went missing")
}
// re-syncing the file moves the stamp; the token does not change
f.putAt("dev1", "wiki/notes.md", "# Notes\n\nhello **team**", when.AddDate(0, 0, 40))
rec = do(t, h, "GET", "/s/"+token, nil)
if !strings.Contains(rec.Body.String(), "Last updated 23 Apr 2026") {
t.Fatalf("stamp must track the file, got %s", rec.Body)
}
// zero time: no stamp at all, not a 1970 date
f.putAt("dev1", "wiki/notes.md", "# Notes\n\nhello **team**", time.Time{})
rec = do(t, h, "GET", "/s/"+token, nil)
if strings.Contains(rec.Body.String(), "Last updated") || strings.Contains(rec.Body.String(), `class="updated"`) {
t.Fatalf("zero time must print no stamp, got %s", rec.Body)
}
// HTML shares are served byte-for-byte — never injected into
htmlTok, _ := authedShare(t, srv, h, p.ID, "wiki/report.html")
rec = do(t, h, "GET", "/s/"+htmlTok, nil)
if !bytes.Equal(rec.Body.Bytes(), []byte(rawHTML)) {
t.Fatalf("html share must be byte-identical, got %q", rec.Body)
}
// so are binaries, Content-Length included
pdfTok, _ := authedShare(t, srv, h, p.ID, "wiki/deck.pdf")
rec = do(t, h, "GET", "/s/"+pdfTok, nil)
if !bytes.Equal(rec.Body.Bytes(), []byte(pdf)) {
t.Fatalf("binary share must be unchanged, got %q", rec.Body)
}
if cl := rec.Header().Get("Content-Length"); cl != fmt.Sprint(len(pdf)) {
t.Fatalf("Content-Length = %q, want %d", cl, len(pdf))
}
}
func jsonReq(t *testing.T, method, url string, body any) *http.Request {
t.Helper()
var data []byte