| 1 |
|
package web |
| 2 |
|
|
| 3 |
|
import ( |
| 4 |
|
"context" |
| 5 |
|
"log/slog" |
| 6 |
|
"net/http" |
| 7 |
|
"net/url" |
| 8 |
|
|
| 9 |
|
"go.bigb.es/auxilia/scribe" |
| 10 |
|
|
| 11 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/beads" |
| 12 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/browse" |
| 13 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/core" |
| 14 |
|
) |
| 15 |
|
|
| 16 |
|
// memoryView renders the memories `bd remember` writes: config rows keyed |
| 17 |
|
// kv.memory.<slug>, each with the revision its value was last written at. The |
| 18 |
|
// row itself is (key, value) and carries no timestamp, so the date comes out of |
| 19 |
|
// the history — see beads.BuildMemories for the walk that finds it. |
| 20 |
|
// |
| 21 |
|
// The reading is not here. It lives in the beads package, which the MCP |
| 22 |
|
// surface's list_memories shares; this type is only the View adapter — slug, |
| 23 |
|
// label, template, and the hand-off of the request's ref and query. |
| 24 |
|
type memoryView struct { |
| 25 |
|
// links is the cross-database link index's cache, held for the same reasons |
| 26 |
|
// beadsView holds one (see web/beads.go): the view is this rendering's one |
| 27 |
|
// piece of per-process state, a zero value is a working cache, and what is |
| 28 |
|
// cached is a fact about a database rather than about who may read it. |
| 29 |
|
// |
| 30 |
|
// It is a second cache and not the board's, because it is a second page: the |
| 31 |
|
// two are read at different times and neither should be able to expire the |
| 32 |
|
// other's entries. What they share is the projection's shape, which is why |
| 33 |
|
// they share its type. |
| 34 |
|
links beads.PrefixCache |
| 35 |
|
} |
| 36 |
|
|
| 37 |
48 |
func (*memoryView) Name() string { return "memory" } |
| 38 |
30 |
func (*memoryView) Label() string { return "Memory" } |
| 39 |
15 |
func (*memoryView) Template() string { return "memory.html" } |
| 40 |
|
|
| 41 |
|
// Applies is the beads fingerprint plus a config table carrying key and value; |
| 42 |
|
// see beads.AppliesMemories. Applies sees table shapes and never rows, so a |
| 43 |
|
// tracker that has never had a memory written into it still gets the tab and |
| 44 |
|
// renders an empty state — the same contract Milestones has. |
| 45 |
37 |
func (*memoryView) Applies(tables []browse.TableInfo) bool { return beads.AppliesMemories(tables) } |
| 46 |
|
|
| 47 |
|
// Build reads the memories and dates them from the history. The result is a |
| 48 |
|
// *beads.MemoryView, handed to memory.html as its .Data. |
| 49 |
|
// |
| 50 |
|
// The clock is passed in rather than read inside the projection: staleness is |
| 51 |
|
// the one thing on this page that depends on when it was rendered, and timeNow |
| 52 |
|
// is the same swappable clock the freshness line uses, so a test pins both at |
| 53 |
|
// once. |
| 54 |
15 |
func (*memoryView) Build(ctx context.Context, sess BrowseSession, _ *core.Repo, ref string, query url.Values) (any, error) { |
| 55 |
15 |
data, err := beads.BuildMemories(ctx, sess, ref, query, timeNow()) |
| 56 |
15 |
if err != nil { |
| 57 |
0 |
return nil, err |
| 58 |
0 |
} |
| 59 |
15 |
return data, nil |
| 60 |
|
} |
| 61 |
|
|
| 62 |
|
// memoryCrossLinks builds the link index the memory bodies are rendered against, |
| 63 |
|
// and returns nil for every page that is not the memory view. A nil one still |
| 64 |
|
// renders the markdown; what it costs is the links, which is what an unreadable |
| 65 |
|
// listing may cost and no more (see linkableDatabases). |
| 66 |
|
// |
| 67 |
|
// It is asked for by this page rather than built into the envelope because |
| 68 |
|
// building it opens a store per database the caller may browse — the same reason |
| 69 |
|
// the issue links are asked for by the detail pane alone. |
| 70 |
|
// |
| 71 |
|
// The index answers both halves of what a memory body cites: the [[slug]] |
| 72 |
|
// references and the issue ids in the prose. They come out of one read of one |
| 73 |
|
// table per database, which is why there is one index here and not two. |
| 74 |
65 |
func (a *app) memoryCrossLinks(r *http.Request, view View, repo *core.Repo) *memoryLinks { |
| 75 |
65 |
mv, ok := view.(*memoryView) |
| 76 |
65 |
if !ok { |
| 77 |
50 |
return nil |
| 78 |
50 |
} |
| 79 |
15 |
dbs, open, ok := a.linkableDatabases(r, repo) |
| 80 |
15 |
if !ok { |
| 81 |
1 |
return nil |
| 82 |
1 |
} |
| 83 |
|
|
| 84 |
14 |
index := beads.PrefixesAcross(r.Context(), dbs, open, &mv.links, timeNow()) |
| 85 |
14 |
|
| 86 |
14 |
// A store that cannot be read is a fact about this deployment and belongs in |
| 87 |
14 |
// the log with its error. The page says nothing at all: the references that |
| 88 |
14 |
// database holds simply stop resolving, exactly as they do for a database this |
| 89 |
14 |
// caller may not browse. |
| 90 |
14 |
for _, f := range index.Failed { |
| 91 |
0 |
slog.Warn("reading a database for the memory link index failed", |
| 92 |
0 |
"component", "web", "database", f.Database.Slug(), scribe.Err(f.Err)) |
| 93 |
0 |
} |
| 94 |
14 |
return &memoryLinks{index: index} |
| 95 |
|
} |