2026-01-27 13:48:59 -05:00
|
|
|
{% extends "core/base.html" %}
|
2026-03-03 16:10:49 -05:00
|
|
|
{% load lyric_extras %}
|
2026-01-27 13:48:59 -05:00
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
{% block title_text %}Dashpost{% endblock title_text %}
|
|
|
|
|
{% block header_text %}<span>Dash</span>post{% endblock header_text %}
|
2026-01-27 13:48:59 -05:00
|
|
|
|
|
|
|
|
|
2026-02-08 21:43:58 -05:00
|
|
|
{% block extra_header %}
|
brief sprint C1: relocate Post + Line from dashboard → billboard (no behavior change) — TDD
The Post/Line models always read more like billboard tenants than dashboard ones (1st-person personal vs. 2nd-person provenance feed); the upcoming Brief model needs them in the billboard namespace as the canonical surface they FK into. C1 is a pure relocation w. zero new behavior: 789 ITs + 20 sky/Post FTs green against the moved code.
- billboard.models adds Post (owner + shared_with) + Line (text + post FK), schema mirroring the legacy dashboard models 1:1; Post.get_absolute_url now reverses to `billboard:view_post`.
- billboard.forms adds LineForm + ExistingPostLineForm (moved from dashboard.forms; dashboard/forms.py removed).
- billboard.views absorbs new_post / view_post / share_post / my_posts (templates rendered from apps/billboard/post.html + my_posts.html).
- billboard.urls adds the namespaced routes: /billboard/new-post, /billboard/post/<uuid>/, /billboard/post/<uuid>/share-post, /billboard/users/<uuid>/. dashboard.urls drops the corresponding entries.
- _applet-my-posts + _applet-new-post URL refs now use the billboard: namespace; templates/apps/dashboard/{post,my_posts}.html removed.
- api/serializers + api/views + api/tests/integrated/test_views imports flip dashboard.models → billboard.models (PostSerializer / PostDetailAPI / PostLinesAPI / PostsAPI all retain identifiers — the model rename to Brief lands in C2).
- dashboard/tests/integrated/test_{models,views,forms} + dashboard/tests/unit/test_{models,forms} swap imports; test_views URL strings flip /dashboard/post/ → /billboard/post/, /dashboard/new_post → /billboard/new-post, /dashboard/users/ → /billboard/users/, share_post → share-post (path) / billboard:share_post (reverser). Tests stay in dashboard.tests/ for now — relocation TBD.
- functional_tests/my_posts_page.py URL string flips to /billboard/users/.
- Auto-generated migrations: billboard/0001_initial (CreateModel Post + Line), dashboard/0003_remove_post_* (drops legacy Post + Line), drama/0004_alter_gameevent_verb (incidental — choices field caught up).
This commit drops the dashboard Post/Line tables w/o data preservation; user has confirmed staging-side wipe is acceptable. C2 introduces the Brief model + read-tracking + slide-down banner unification. C3 hooks Note-unlock + share-post-invite + magic-link / invalid-link `messages` calls into the new Brief / banner pipeline.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:20:06 -04:00
|
|
|
{% url "billboard:view_post" post.id as form_action %}
|
2026-02-08 21:43:58 -05:00
|
|
|
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %}
|
|
|
|
|
{% endblock extra_header %}
|
2026-01-27 13:48:59 -05:00
|
|
|
|
2026-02-08 21:43:58 -05:00
|
|
|
{% block content %}
|
|
|
|
|
<div class="row justify-content-center">
|
|
|
|
|
<div class="col-lg-6">
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
<small>Post created by: <span id="id_post_owner">{{ post.owner|display_name }}</span></small>
|
|
|
|
|
<table id="id_post_table" class="table">
|
|
|
|
|
{% for line in post.lines.all %}
|
|
|
|
|
<tr><td>{{ forloop.counter }}. {{ line.text }}</td></tr>
|
2026-02-08 21:43:58 -05:00
|
|
|
{% endfor %}
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-18 13:53:05 -05:00
|
|
|
|
|
|
|
|
<div class="row justify-content-center">
|
|
|
|
|
<div class="col-lg-6">
|
|
|
|
|
|
brief sprint C3.b+c+d+e: share-post Line+Brief async, magic-link / invalid-link banners use Brief styling, .alert-* retired — TDD
Closes the C3 brief sprint. Three event sources (note unlock, share invite, login messages) now route through the Brief slide-down, & the legacy .alert-success/.alert-warning rendering in base.html is retired.
C3.b — share-post async Line + Brief:
- billboard.share_post detects Accept: application/json. JSON path appends a Line (text="Shared with X at <isoformat>", isoformat carries microseconds so two rapid shares of the same email don't collide on Line.unique_together(post,text)), spawns a Brief(kind=SHARE_INVITE) for the sharer, and returns {brief: brief.to_banner_dict() | None, line_text, recipient_display}. Sharer-shares-with-themselves stays a silent no-op (response carries brief: null). Legacy form-submit path preserved for non-AJAX (still redirects + flashes the privacy-safe message — kept for older FTs / no-JS fallback).
- billboard.Brief.to_banner_dict() (moved from dashboard.views helper to a model method) shapes the JSON the banner JS consumes.
- post.html: share form intercepted by JS — fetches POST w. Accept:application/json, then appends `data.line_text` as the next row in #id_post_table, calls Brief.showBanner(data.brief), and (when registered) appends a fresh `<span class="post-recipient">` to the new #id_post_recipients box. No page reload — the alert-success flash is gone.
- 10 new ITs (SharePostAsyncTest + SharePostLegacyRedirectTest) cover the JSON path, line append, brief creation w. SHARE_INVITE kind, registered/unregistered recipient behaviour, sharer-self skip, line dedupe via timestamp, and that the legacy form-submit redirect path still works.
- functional_tests.test_sharing line numbering updated: the share now records its own Line so the alice-reply lands at row 3 instead of 2.
C3.c+d — magic-link confirmation + invalid-link error use Brief banner styling:
- base.html's {% if messages %} block stops rendering .alert-success/.alert-warning divs. Instead each message renders as a transient Brief-styled banner: <div class="note-banner note-banner--message note-banner--{{level_tag}}"> with .note-banner__body / __description carrying the message text and a .btn-cancel NVM that removes the banner via inline onclick. No DB Brief row; no FYI; no square. Same Gaussian-glass look as note-unlock + share-invite Briefs.
- _note.scss adds the note-banner--message variant (full-opacity description) + note-banner--error/--warning border-color override (priRd 0.6) so the invalid-link banner reads as red/abandon.
C3.e — .alert-success/.alert-warning retired in markup; the SCSS class blocks aren't referenced anywhere else in templates so they sit dormant (left in place — base form styling keeps .form-control etc. working; no need to ripple into _base.scss).
Banner JS (note.js / Brief module) was untouched in C3.b+c+d — the Brief.showBanner contract from C3.a already handles all three kinds (NOTE_UNLOCK / USER_POST / SHARE_INVITE) by reading kind off the brief; the message-banner path doesn't go through showBanner because there's no Brief row.
Tests: 218 dashboard+billboard+api ITs + 322 lyric+dashboard+billboard ITs + 2 sharing FTs + 9 my_notes FTs + 1 Jasmine FT all green. Existing lyric.test_views login message-text assertions unchanged (they pull from messages framework — not the rendered HTML — so the markup swap doesn't affect them).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:15:43 -04:00
|
|
|
<form id="id_share_form" method="POST" action="{% url "billboard:share_post" post.id %}">
|
2026-02-18 13:53:05 -05:00
|
|
|
{% csrf_token %}
|
|
|
|
|
<input
|
|
|
|
|
id="id_recipient"
|
|
|
|
|
name="recipient"
|
2026-03-02 15:45:12 -05:00
|
|
|
class="form-control form-control-lg{% if form.errors.recipient %} is-invalid{% endif %}"
|
2026-02-18 13:53:05 -05:00
|
|
|
placeholder="friend@example.com"
|
|
|
|
|
aria-describedby="id_recipient_feedback"
|
|
|
|
|
required
|
|
|
|
|
/>
|
2026-03-02 15:45:12 -05:00
|
|
|
{% if form.errors.recipient %}
|
2026-02-18 13:53:05 -05:00
|
|
|
<div id="id_recipient_feedback" class="invalid-feedback">
|
|
|
|
|
{{ form.errors.recipient.0 }}
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
2026-04-06 03:02:37 -04:00
|
|
|
<button type="submit" class="btn btn-primary">Share</button>
|
2026-02-18 13:53:05 -05:00
|
|
|
</form>
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
<small>Post shared with:
|
brief sprint C3.b+c+d+e: share-post Line+Brief async, magic-link / invalid-link banners use Brief styling, .alert-* retired — TDD
Closes the C3 brief sprint. Three event sources (note unlock, share invite, login messages) now route through the Brief slide-down, & the legacy .alert-success/.alert-warning rendering in base.html is retired.
C3.b — share-post async Line + Brief:
- billboard.share_post detects Accept: application/json. JSON path appends a Line (text="Shared with X at <isoformat>", isoformat carries microseconds so two rapid shares of the same email don't collide on Line.unique_together(post,text)), spawns a Brief(kind=SHARE_INVITE) for the sharer, and returns {brief: brief.to_banner_dict() | None, line_text, recipient_display}. Sharer-shares-with-themselves stays a silent no-op (response carries brief: null). Legacy form-submit path preserved for non-AJAX (still redirects + flashes the privacy-safe message — kept for older FTs / no-JS fallback).
- billboard.Brief.to_banner_dict() (moved from dashboard.views helper to a model method) shapes the JSON the banner JS consumes.
- post.html: share form intercepted by JS — fetches POST w. Accept:application/json, then appends `data.line_text` as the next row in #id_post_table, calls Brief.showBanner(data.brief), and (when registered) appends a fresh `<span class="post-recipient">` to the new #id_post_recipients box. No page reload — the alert-success flash is gone.
- 10 new ITs (SharePostAsyncTest + SharePostLegacyRedirectTest) cover the JSON path, line append, brief creation w. SHARE_INVITE kind, registered/unregistered recipient behaviour, sharer-self skip, line dedupe via timestamp, and that the legacy form-submit redirect path still works.
- functional_tests.test_sharing line numbering updated: the share now records its own Line so the alice-reply lands at row 3 instead of 2.
C3.c+d — magic-link confirmation + invalid-link error use Brief banner styling:
- base.html's {% if messages %} block stops rendering .alert-success/.alert-warning divs. Instead each message renders as a transient Brief-styled banner: <div class="note-banner note-banner--message note-banner--{{level_tag}}"> with .note-banner__body / __description carrying the message text and a .btn-cancel NVM that removes the banner via inline onclick. No DB Brief row; no FYI; no square. Same Gaussian-glass look as note-unlock + share-invite Briefs.
- _note.scss adds the note-banner--message variant (full-opacity description) + note-banner--error/--warning border-color override (priRd 0.6) so the invalid-link banner reads as red/abandon.
C3.e — .alert-success/.alert-warning retired in markup; the SCSS class blocks aren't referenced anywhere else in templates so they sit dormant (left in place — base form styling keeps .form-control etc. working; no need to ripple into _base.scss).
Banner JS (note.js / Brief module) was untouched in C3.b+c+d — the Brief.showBanner contract from C3.a already handles all three kinds (NOTE_UNLOCK / USER_POST / SHARE_INVITE) by reading kind off the brief; the message-banner path doesn't go through showBanner because there's no Brief row.
Tests: 218 dashboard+billboard+api ITs + 322 lyric+dashboard+billboard ITs + 2 sharing FTs + 9 my_notes FTs + 1 Jasmine FT all green. Existing lyric.test_views login message-text assertions unchanged (they pull from messages framework — not the rendered HTML — so the markup swap doesn't affect them).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:15:43 -04:00
|
|
|
<span id="id_post_recipients">
|
|
|
|
|
{% for user in post.shared_with.all %}
|
|
|
|
|
<span class="post-recipient">{{ user|display_name }}</span>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</span>
|
2026-02-18 13:53:05 -05:00
|
|
|
</small>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-08 21:43:58 -05:00
|
|
|
{% endblock content %}
|
|
|
|
|
|
|
|
|
|
{% block scripts %}
|
|
|
|
|
{% include "apps/dashboard/_partials/_scripts.html" %}
|
brief sprint C3.b+c+d+e: share-post Line+Brief async, magic-link / invalid-link banners use Brief styling, .alert-* retired — TDD
Closes the C3 brief sprint. Three event sources (note unlock, share invite, login messages) now route through the Brief slide-down, & the legacy .alert-success/.alert-warning rendering in base.html is retired.
C3.b — share-post async Line + Brief:
- billboard.share_post detects Accept: application/json. JSON path appends a Line (text="Shared with X at <isoformat>", isoformat carries microseconds so two rapid shares of the same email don't collide on Line.unique_together(post,text)), spawns a Brief(kind=SHARE_INVITE) for the sharer, and returns {brief: brief.to_banner_dict() | None, line_text, recipient_display}. Sharer-shares-with-themselves stays a silent no-op (response carries brief: null). Legacy form-submit path preserved for non-AJAX (still redirects + flashes the privacy-safe message — kept for older FTs / no-JS fallback).
- billboard.Brief.to_banner_dict() (moved from dashboard.views helper to a model method) shapes the JSON the banner JS consumes.
- post.html: share form intercepted by JS — fetches POST w. Accept:application/json, then appends `data.line_text` as the next row in #id_post_table, calls Brief.showBanner(data.brief), and (when registered) appends a fresh `<span class="post-recipient">` to the new #id_post_recipients box. No page reload — the alert-success flash is gone.
- 10 new ITs (SharePostAsyncTest + SharePostLegacyRedirectTest) cover the JSON path, line append, brief creation w. SHARE_INVITE kind, registered/unregistered recipient behaviour, sharer-self skip, line dedupe via timestamp, and that the legacy form-submit redirect path still works.
- functional_tests.test_sharing line numbering updated: the share now records its own Line so the alice-reply lands at row 3 instead of 2.
C3.c+d — magic-link confirmation + invalid-link error use Brief banner styling:
- base.html's {% if messages %} block stops rendering .alert-success/.alert-warning divs. Instead each message renders as a transient Brief-styled banner: <div class="note-banner note-banner--message note-banner--{{level_tag}}"> with .note-banner__body / __description carrying the message text and a .btn-cancel NVM that removes the banner via inline onclick. No DB Brief row; no FYI; no square. Same Gaussian-glass look as note-unlock + share-invite Briefs.
- _note.scss adds the note-banner--message variant (full-opacity description) + note-banner--error/--warning border-color override (priRd 0.6) so the invalid-link banner reads as red/abandon.
C3.e — .alert-success/.alert-warning retired in markup; the SCSS class blocks aren't referenced anywhere else in templates so they sit dormant (left in place — base form styling keeps .form-control etc. working; no need to ripple into _base.scss).
Banner JS (note.js / Brief module) was untouched in C3.b+c+d — the Brief.showBanner contract from C3.a already handles all three kinds (NOTE_UNLOCK / USER_POST / SHARE_INVITE) by reading kind off the brief; the message-banner path doesn't go through showBanner because there's no Brief row.
Tests: 218 dashboard+billboard+api ITs + 322 lyric+dashboard+billboard ITs + 2 sharing FTs + 9 my_notes FTs + 1 Jasmine FT all green. Existing lyric.test_views login message-text assertions unchanged (they pull from messages framework — not the rendered HTML — so the markup swap doesn't affect them).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:15:43 -04:00
|
|
|
<script>
|
|
|
|
|
// Async share: intercepts the share form, POSTs w. Accept:application/json,
|
|
|
|
|
// then slide-downs the SHARE_INVITE Brief banner under the navbar h2 + appends
|
|
|
|
|
// the freshly-recorded Line into #id_post_table. No page reload — the legacy
|
|
|
|
|
// alert-success flash is gone.
|
|
|
|
|
(function () {
|
|
|
|
|
'use strict';
|
|
|
|
|
var form = document.getElementById('id_share_form');
|
|
|
|
|
var input = document.getElementById('id_recipient');
|
|
|
|
|
var table = document.getElementById('id_post_table');
|
|
|
|
|
var recipientsBox = document.getElementById('id_post_recipients');
|
|
|
|
|
if (!form || !input || !table) return;
|
|
|
|
|
|
|
|
|
|
function _csrf() {
|
|
|
|
|
var m = document.cookie.match(/csrftoken=([^;]+)/);
|
|
|
|
|
return m ? m[1] : '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _appendLine(text) {
|
|
|
|
|
var n = table.querySelectorAll('tr').length + 1;
|
|
|
|
|
var tr = document.createElement('tr');
|
|
|
|
|
var td = document.createElement('td');
|
|
|
|
|
td.textContent = n + '. ' + text;
|
|
|
|
|
tr.appendChild(td);
|
|
|
|
|
table.appendChild(tr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var fd = new FormData(form);
|
|
|
|
|
fetch(form.action, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'same-origin',
|
|
|
|
|
headers: {
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
'X-CSRFToken': _csrf(),
|
|
|
|
|
},
|
|
|
|
|
body: fd,
|
|
|
|
|
})
|
|
|
|
|
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
|
|
|
|
.then(function (data) {
|
|
|
|
|
if (data.line_text) _appendLine(data.line_text);
|
|
|
|
|
if (window.Brief && data.brief) Brief.showBanner(data.brief);
|
|
|
|
|
if (data.recipient_display && recipientsBox) {
|
|
|
|
|
var span = document.createElement('span');
|
|
|
|
|
span.className = 'post-recipient';
|
|
|
|
|
span.textContent = data.recipient_display;
|
|
|
|
|
recipientsBox.appendChild(document.createTextNode(' '));
|
|
|
|
|
recipientsBox.appendChild(span);
|
|
|
|
|
}
|
|
|
|
|
input.value = '';
|
|
|
|
|
})
|
|
|
|
|
.catch(function () {
|
|
|
|
|
// No-op for now — the privacy-safe response shape means
|
|
|
|
|
// even an unregistered recipient is a 200 w. brief data;
|
|
|
|
|
// a true error path (5xx) silently swallows.
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}());
|
|
|
|
|
</script>
|
2026-02-08 21:43:58 -05:00
|
|
|
{% endblock scripts %}
|