| 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 | |