` w. anchor on the handle #}
+{# routing to the bud's landing page + data-tt-* attrs the tooltip #}
+{# portal reads on row-lock click. `item.shoptalk_text` + `.milestone_ #}
+{# dt` are attached by the my_buds view from BudshipNote. #}
+
+ {{ item|at_handle }}
+ the {{ item.active_title_display }}
diff --git a/src/templates/apps/billboard/bud.html b/src/templates/apps/billboard/bud.html
new file mode 100644
index 0000000..cdc5a6b
--- /dev/null
+++ b/src/templates/apps/billboard/bud.html
@@ -0,0 +1,85 @@
+{% extends "core/base.html" %}
+{% load static %}
+{% load lyric_extras %}
+
+{% block title_text %}Billbud{% endblock title_text %}
+{% block header_text %}Billbud{% endblock header_text %}
+
+{% block content %}
+{# note.js exposes window.Brief — needed by the auto-fired @mailman Brief #}
+{# when this page is the destination of an invite cascade. #}
+
+
+
+
+
+
+
+ {# 4-btn apparatus mirrors my_sea.html: kit (from base.html) + bud + #}
+ {# gear + burger. Apparatus loads bud-btn.js (defines window. #}
+ {# bindBudBtn — does NOT auto-bind); the inline script below opens #}
+ {# the panel + stubs OK to disabled per the sprint plan. #}
+ {% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Bud" include_suggestions=False %}
+
+
+ {% include "apps/billboard/_partials/_bud_gear.html" %}
+
+ {# Burger fan — sea_btn_active + sea_first_draw_pending drive the #}
+ {# server-side .active / .glow-handoff classes (same vars my_sea + #}
+ {# room read). burger-btn.js auto-binds on DOMContentLoaded. #}
+ {% include "apps/gameboard/_partials/_burger.html" %}
+
+
+
+{% if pending_invite %}
+
+{% endif %}
+{% endblock content %}
diff --git a/src/templates/apps/billboard/my_buds.html b/src/templates/apps/billboard/my_buds.html
index e498b45..7903f0a 100644
--- a/src/templates/apps/billboard/my_buds.html
+++ b/src/templates/apps/billboard/my_buds.html
@@ -11,6 +11,10 @@
{% include "apps/applets/_partials/_applet-list-shell.html" with shell_title="My Buds" shell_list_id="id_buds_list" shell_items=buds shell_item_template="apps/billboard/_partials/_my_buds_item.html" shell_empty="No buds yet." %}
+{# Tooltip portal — populated from .bud-entry data-tt-* attrs on row #}
+{# click. See my-buds-tooltip.js for the bind logic. #}
+{% include "apps/billboard/_partials/_bud_tooltip.html" %}
+
{# Bud btn (bottom-left) + slide-out add-bud panel — async POST to add_bud. #}
{% include "apps/billboard/_partials/_bud_add_panel.html" %}
{% endblock content %}
@@ -19,4 +23,5 @@
{# Brief module — needed by _bud_add_panel's OK handler so the #}
{# duplicate-add error banner can render via Brief.showDuplicateBanner.#}
+
{% endblock scripts %}
diff --git a/src/templates/apps/billboard/post.html b/src/templates/apps/billboard/post.html
index 949cbd5..d12c5ba 100644
--- a/src/templates/apps/billboard/post.html
+++ b/src/templates/apps/billboard/post.html
@@ -41,12 +41,14 @@
{% for line in post.lines.all %}
{{ line.author|at_handle }}
- {# adman / taxman-authored Lines (note unlock, share invite, tax ledger system prose) may carry an `` anchor that needs to render as HTML. User-typed Lines stay escaped. `display_text` strips the `[] ` prefix that tax-ledger Lines carry to satisfy `unique_together = (post, text)` — the per-line `created_at` timestamp on the right renders the user-facing moment. #}{% if line.author.username == 'adman' or line.author.username == 'taxman' or line.author.username == 'mailman' %}{{ line.display_text|safe }}{% else %}{{ line.display_text }}{% endif %}
+ {# adman / taxman / mailman-authored Lines (note unlock, share invite, tax ledger, invite cascade) may carry HTML anchors (note-ref / post-attribution). User-typed Lines stay escaped. `display_text` strips the `[] ` prefix that tax-ledger Lines carry to satisfy `unique_together = (post, text)` — the per-line `created_at` timestamp on the right renders the user-facing moment. #}{% if line.author.username == 'adman' or line.author.username == 'taxman' or line.author.username == 'mailman' %}{{ line.display_text|safe }}{% else %}{{ line.display_text }}{% endif %}
- {# @mailman invite Lines carry an OK/BYE action block driven by #}
- {# the linked SeaInvite's status (my-sea invite flow). Non-invite #}
- {# system + user Lines have no `sea_invite`, so this is skipped. #}
- {% if line.sea_invite %}{% include "apps/billboard/_partials/_invite_actions.html" %}{% endif %}
+ {# Pre-sprint @mailman invite Lines carried an in-line OK/BYE #}
+ {# block via _invite_actions.html. Bud landing page sprint #}
+ {# 2026-05-27 migrates that interaction onto bud.html — the #}
+ {# Line's prose now embeds a post-attribution anchor (see #}
+ {# apps.billboard.mail.INVITE_TEMPLATE) that routes to the #}
+ {# owner's bud page where accept/decline/spectator live. #}
{% endfor %}
diff --git a/src/templates/core/base.html b/src/templates/core/base.html
index dbc98ee..bdb7517 100644
--- a/src/templates/core/base.html
+++ b/src/templates/core/base.html
@@ -73,6 +73,24 @@
{% block scripts %}
{% endblock scripts %}
+ {# Cross-page @mailman Brief surface — bud landing page sprint #}
+ {# 2026-05-27. Server-side context processor injects mail_ #}
+ {# brief_payload on every authenticated response when the user #}
+ {# has an unread MAIL_ACCEPTANCE Brief; banner auto-fires here #}
+ {# (note.js is included by pages that need Brief earlier; this #}
+ {# loads it if no page-block scripts pulled it in already). #}
+ {% if mail_brief_payload %}
+ {{ mail_brief_payload|json_script:"id_mail_brief_payload" }}
+
+
+ {% endif %}