Files
python-tdd/src/templates/apps/billboard/post.html
Disco DeDisco 246e45e55d buds rename + applet-list shell — Buddies → Buds everywhere (model field, slug, URL, view, DOM, CSS); my_buds.html + my_posts.html share new _applet-list-shell.html partial — vertical-title applet-scroll card; my_posts hosts two side-by-side in landscape, stacked in portrait — TDD
- lyric/0005 RemoveField+AddField (RenameField doesn't rename the implicit M2M through table; field was new in 0004 so no data loss). Lyric.User.buddies → User.buds; related_name added_as_buddy → added_as_bud.
  - applets/0007 renames Applet slug my-buddies → my-buds + name 'My Buddies' → 'My Buds'. UI rationale: BILLBUDDIES overflowed the page-header band; in-game term collapses to BILLBUDS.
  - billboard/0006 alter Line.Meta.ordering = ('created_at', 'id') — was already in models.py, just generates the corresponding migration (formalizing the ordering decision from the May-8b refactor).
  - global rename via sed: buddies → buds, buddy → bud across 16 files (templates, SCSS, JS, ITs, FTs, page object, view code). 4 file renames via git mv: my_buddies.html → my_buds.html, _applet-my-buddies.html → _applet-my-buds.html, _buddy_panel.html → _bud_panel.html, _buddy_add_panel.html → _bud_add_panel.html, _buddy.scss → _bud.scss. Test files renamed too: test_buddies.py → test_buds.py, test_my_buddies.py → test_my_buds.py, test_buddy_btn.py → test_bud_btn.py. core.scss @import 'buddy' → 'bud'.
  - new shared partial templates/apps/applets/_partials/_applet-list-shell.html — vertical-rotated <h2> + scrollable <ul> aperture, parameterised via {% include %} so a single page can invoke it more than once. Params: shell_title, shell_items, shell_item_template, shell_list_id, shell_empty.
  - my_buds.html: single shell invocation w. add-bud panel below (page_class page-billbuds).
  - my_posts.html: two shell invocations (own posts + posts shared with me) inside .applet-list-page--two-up — portrait stacks them; landscape lays side-by-side via @media (orientation: landscape) flex-direction: row (page_class page-billposts).
  - SCSS: drop the bottom-anchored .buds-page block; new shared .applet-list-page (extends %billboard-page-base, flex-column + padding) w. .applet-scroll inside (extends %applet-box) and .applet-list inside that (flex: 1, overflow-y: auto). .applet-list-page--two-up flips to row layout in landscape. Body class trio gains page-billposts.
  - 841 ITs + 5 my_buds/my_posts FTs green.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 23:08:33 -04:00

84 lines
3.9 KiB
HTML

{% extends "core/base.html" %}
{% load lyric_extras %}
{% block title_text %}Billpost{% endblock title_text %}
{% block header_text %}<span>Bill</span>post{% endblock header_text %}
{% block content %}
{# Hidden owner span — preserves the existing FT contract that reads the #}
{# post owner via #id_post_owner. Visible owner attribution moved into the #}
{# self line ("just me, …" / "& me, …") below. #}
<span id="id_post_owner" hidden>{{ post.owner|display_name }}</span>
<div class="post-page">
<header class="post-header">
<h3 class="post-title">{{ post.title }}</h3>
{% with recipients=post.shared_with.all %}
{% if recipients %}
<p class="post-shared-recipients">shared between {% for r in recipients %}<span class="post-recipient">{{ r|display_name }}</span>{% if not forloop.last %}, {% endif %}{% endfor %}</p>
<p class="post-shared-self">&amp; me, {{ post.owner|display_name }} the {{ post.owner.active_title_display }}</p>
{% else %}
<p class="post-shared-self">just me, {{ post.owner|display_name }} the {{ post.owner.active_title_display }}</p>
{% endif %}
{% endwith %}
</header>
<ul id="id_post_table" class="post-lines">
{% for line in post.lines.all %}
<li class="post-line {% if line.author.username == 'adman' %}post-line--system{% endif %}">
<span class="post-line-author">{{ line.author|display_name }}</span>
<span class="post-line-text">{# adman-authored Lines (note unlock + share invite system prose) carry an `<a class="note-ref">` anchor that needs to render as HTML. User-typed Lines stay escaped. #}{% if line.author.username == 'adman' %}{{ line.text|safe }}{% else %}{{ line.text }}{% endif %}</span>
<time class="post-line-time" datetime="{{ line.created_at|date:'c' }}">{{ line.created_at|date:'g:i A' }}</time>
</li>
{% endfor %}
<li class="post-line-buffer" aria-hidden="true"></li>
</ul>
{# Admin-Post (note-unlock thread) input is read-only: the user can't #}
{# respond, and the placeholder calls that out. View_post hard-rejects #}
{# POSTs to NOTE_UNLOCK posts; the post_save Line signal is the safety #}
{# net for ORM-level / API writes that bypass the view. #}
{% if post.kind == 'note_unlock' %}
<form id="id_post_line_form" class="post-line-form">
<input
id="id_post_line_text"
name="text"
class="form-control"
placeholder="No response needed at this time"
readonly
/>
</form>
{% else %}
<form id="id_post_line_form" method="POST" action="{% url 'billboard:view_post' post.id %}" class="post-line-form">
{% csrf_token %}
<input
id="id_post_line_text"
name="text"
class="form-control{% if form.errors.text %} is-invalid{% endif %}"
placeholder="Enter a post line"
value="{{ form.text.value|default:'' }}"
aria-describedby="id_post_line_feedback"
required
/>
{% if form.errors %}
<div id="id_post_line_feedback" class="invalid-feedback">
{{ form.errors.text.0 }}
</div>
{% endif %}
</form>
{% endif %}
{# Bud btn (bottom-left) + slide-out recipient field — async share. #}
{# Suppressed on admin Posts (note unlock thread) since friend-invites #}
{# don't apply to system-authored threads. #}
{% if post.kind != 'note_unlock' %}
{% include "apps/billboard/_partials/_bud_panel.html" %}
{% endif %}
</div>
{% endblock content %}
{% block scripts %}
{% include "apps/dashboard/_partials/_scripts.html" %}
{% endblock scripts %}