oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1{{/* The results region, rendered two ways from one definition.
2
3 index.html includes it, so a scriptless form submit gets it inside
4 the page; GET /search executes it alone, so search.js can swap it in
5 on every keystroke. One definition because the two must agree about
6 which result is primed — if they disagreed, where Enter goes would
7 depend on whether the script had loaded yet. */}}
8{{/* One sentence, two places that must not disagree: the callout the
9 server renders when store.Search fails, and the attribute search.js
10 reads when its own fetch fails. Both mean "the search did not run",
11 and a page with two wordings for that would be two pages. */}}
12{{define "searchFailed"}}The search did not run. Nothing was found because nothing was searched.{{end}}
13
14{{define "results"}}
15{{if .SearchFailed}}
16 <div rst-callout rst-tone="negative">
17 <div rst-callout-body><p>{{template "searchFailed"}}</p></div>
18 </div>
19{{else if .Searched}}
20 {{/* The count heading only when there is something to count. A zero
21 is said once, below, as one quiet line (spec §4): "0 results for
22 “rev”" over a callout was what live search showed on every partial
23 keystroke. */}}
24 {{if .Results}}<h2>{{len .Results}} result{{if ne (len .Results) 1}}s{{end}} for “{{.Query}}”</h2>{{end}}
25 {{/* §6.5's loud withholding, above the results rather than below them.
26
27 A reader who scrolls past the last result has already decided what
28 the page means; the notice has to arrive before the list it is
29 about. What each line says is decided in Go (sibling.Outcome), so
30 one wording lives in one place — and every line names an app and
31 never a document, because naming the document would report the
32 existence of a passage this person may not have. */}}
33 {{if .Notices}}
34 <div rst-callout rst-tone="warning" rst-withheld>
35 <div rst-callout-body>{{range .Notices}}<p>{{.}}</p>{{end}}</div>
36 </div>
37 {{end}}
38 {{/* §8.8's fact box. BELOW the notices and above the list, both
39 deliberately: a confident single answer rendered above the line
40 saying the page is partial is the withholding notice's own
41 argument with the order reversed.
42
43 Inside this define rather than above it, so search.js swaps box
44 and list together. A box outside it would leave query N-1's answer
45 sitting over query N's results on every keystroke.
46
47 Every character here is the document's — the label it wrote, the
48 span byte for byte, its own title and heading trail. The link is
49 the ONLY link, and it must never wear rst-result-title: search.js
50 reads that attribute to decide where Enter goes, and §8.8 rules
51 that Enter still goes to the first result and not to a box. */}}
52 {{if .Facts}}
53 <div rst-fact-box>
54 {{range .Facts}}
55 {{/* A row a model pointed at is marked, and names its model — §8.9:
56 it does not inherit the deterministic layer's guarantees (the
57 model can omit, and "this is an address" is its judgement), so
58 the reader must be able to tell the two kinds of claim apart
59 at a glance. Decision 4: every answer names its provider. */}}
60 <p rst-fact{{if .Model}} rst-fact-model{{end}}>
61 <span rst-fact-label>{{.Label}}</span>
62 <span rst-fact-value>{{.Span}}</span>
63 {{if .Model}}<span rst-fact-by title="Found by a model, not validated">🤖 {{.Provider}} · {{.Model}}</span>{{end}}
64 <a href="/documents/{{.DocumentID}}#p{{.Ordinal}}" rst-fact-cite>{{.Title}}{{if .Trail}} › {{.Trail}}{{end}}</a>
65 </p>
66 {{/* §8.8's narrowing line, under the row it describes: the box
67 chose this row on the reader's own words, and how many it left
68 out. It keeps the others visible, which is the one thing
69 standing between a planted label and a single confident
70 answer. */}}
71 {{with .Note}}<p rst-fact-note>{{.Text}}</p>{{end}}
72 {{end}}
73 </div>
74 {{end}}
75 {{if not .Results}}
76 {{/* Not a callout: one muted line (spec §4). Three sentences, and
77 which one is true depends on why the page is empty. The first —
78 "no matches" — is only true when nothing was withheld and the
79 count could be read: a page emptied by withholding is not an
80 absence, and a count that failed is not a zero (AGENTS.md). Same
81 rule as ever; plainer words.
82
83 rst-search-empty marks it for search.js, which holds it back
84 while the person is still typing: whole-token matching means
85 "rev" finds nothing on the way to "revenue", and saying so on
86 every keystroke is noise, not an answer. A submitted search, and
87 a pause, show it. */}}
88 <p rst-muted rst-search-empty>
89 {{if .Notices}}
90 Nothing that could be shown matched “{{.Query}}”. Some passages were
91 withheld (see above), so there may be more than this.
92 {{else if lt .Chunks 0}}
93 No matches for “{{.Query}}” — but the passage count could not be
94 read, so the index may be empty.
95 {{else}}
96 No matches for “{{.Query}}”.
97 {{end}}
98 </p>
99 {{end}}
100 <ol rst-results>
101 {{range $i, $r := .Results}}
102 <li rst-result{{if and $.Primed (eq $i 0)}} rst-result-primed{{end}}>
103 {{/* Ancestry above the title, heading trail below it. Both, and in
104 that order, because they answer different questions: the path
105 says which part of the workspace this came from, the heading
106 says where in the page. A result showing only the second is
107 two indistinguishable "Week one"s under different handbooks. */}}
108 {{if $r.Path}}<span rst-result-path>{{$r.Path}}</span>{{end}}
109 <a href="/documents/{{$r.DocumentID}}#p{{$r.Ordinal}}" rst-result-title>{{$r.Title}}</a>
110 {{if $r.Heading}}<span rst-result-trail>{{$r.Heading}}</span>{{end}}
111 <p rst-result-snippet>{{$r.Snippet | safeSnippet}}</p>
112 <p rst-result-meta rst-muted>{{$r.Source}} · passage {{$r.Ordinal}}</p>
113 </li>
114 {{end}}
115 </ol>
116{{end}}
117{{end}}
118