buddy btn sprint scaffolding: TDD spec + partial template + SCSS + page_class — 15/16 FTs green, 1 captured-red for post-compaction handoff
Pre-compaction handoff for the bottom-left handshake btn that replaces the inline share form on post.html. The full spec lives in functional_tests/test_buddy_btn.py — running it as a red-first TDD checklist for the next agent (or future Disco) to pick up after compaction.
Scaffolding landed:
- functional_tests/test_buddy_btn.py — 16 tests across 6 classes covering presence-only-on-post.html (B1–B3), bottom-left fixed positioning matching kit-btn dimensions, slide-out panel structure (closed/open width, OK btn = .btn-confirm, recipient input left-padding clears the glyph), kit↔buddy mutual-exclusion via opacity, click-outside + Escape dismiss + clear, OK→async share creates Brief + appends Line + chips the recipient + closes the panel + clears the field, and post.html / my_posts.html body class picks up the aperture marker (page-billboard).
- templates/apps/billboard/_partials/_buddy_panel.html — the partial: <button id="id_buddy_btn"><i class="fa-solid fa-handshake"></i></button> + #id_buddy_panel housing #id_recipient and #id_buddy_ok (.btn.btn-confirm). Inline JS mirrors the game-kit.js click/escape/click-outside pattern, toggles html.buddy-open + .active on the btn, intercepts OK to POST share-post w. Accept:application/json (reuses C3.b shape — line_text + brief.to_banner_dict() + recipient_display), appends the chip + line in-DOM, Brief.showBanner shows the slide-down banner, _close clears the input.
- templates/apps/billboard/post.html — drops the inline #id_share_form / #id_recipient / SHARE-primary block + its JS; includes the buddy panel partial at the end of {% block content %}.
- billboard.views.view_post + my_posts now set page_class="page-billboard" so the body class hooks into the aperture SCSS group (the user noted post.html wasn't in that group; this brings it in).
- static_src/scss/_buddy.scss — new partial: #id_buddy_btn fixed bottom-left mirror of #id_kit_btn (3rem circle, secUser border, .active state, transition: opacity 0.15s); #id_buddy_panel slide-out spans calc(100vw - 3rem) (1.5rem each side w. landscape sidebar carve-outs), transform: scaleX(0)→1 from left center on html.buddy-open, opacity 0→1, the recipient input gets padding 0 1rem 0 3.5rem so the glyph doesn't overlap. Mutual exclusion: html.buddy-open #id_kit_btn → opacity:0; html:has(#id_kit_bag_dialog[open]) #id_buddy_btn → opacity:0 (uses :has() per project convention; no JS-side kit-open class needed).
- core.scss imports buddy after game-kit.
15/16 FTs green; the lone red is BuddyBtnOkSubmitsAsyncShareTest.test_ok_creates_brief_appends_line_and_chip — server flow works (Brief is created, recipient chip + line append in DOM both visible in the screendump), only the .note-banner injection isn't surfacing on post.html. Likely cause: note.js inserts after the first <h2>, but post.html's only h2 is the rotated navbar header which is position:absolute, so the banner's geometry parents to that and falls outside the visible aperture. Two clean follow-ups for the post-compaction agent: (a) make Brief.showBanner pick a different anchor when h2.parentElement is position:absolute, or (b) define a #id_brief_banner_anchor in base.html under the page content and have showBanner prefer it.
Also pending for post-compaction: update functional_tests.post_page.PostPage.share_post_with() to drive the new buddy-btn flow (click btn → type → click OK → wait for chip) so the legacy test_sharing FT keeps working — currently it still operates on the inline form selectors that no longer exist.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
120
src/static_src/scss/_buddy.scss
Normal file
120
src/static_src/scss/_buddy.scss
Normal file
@@ -0,0 +1,120 @@
|
||||
// ── Buddy btn (bottom-left mirror of #id_kit_btn) ─────────────────────────
|
||||
//
|
||||
// Lives on post.html only — slide-out recipient field for the share-post
|
||||
// async flow. Mutually exclusive w. #id_kit_btn (bottom-right): when one is
|
||||
// active (.active class on btn + html.{kit|buddy}-open class on root), the
|
||||
// other quickly fades to opacity 0.
|
||||
//
|
||||
// Spec: functional_tests/test_buddy_btn.py.
|
||||
|
||||
#id_buddy_btn {
|
||||
position: fixed;
|
||||
bottom: 0.5rem;
|
||||
left: 0.5rem;
|
||||
|
||||
@media (orientation: landscape) {
|
||||
left: 1rem;
|
||||
bottom: 0.5rem;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (min-width: 1800px) {
|
||||
left: 2.5rem; // mirror the doubled 8rem sidebar centring
|
||||
}
|
||||
|
||||
z-index: 318;
|
||||
font-size: 1.75rem;
|
||||
cursor: pointer;
|
||||
color: rgba(var(--secUser), 1);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--priUser), 1);
|
||||
border: 0.15rem solid rgba(var(--secUser), 1);
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&.active {
|
||||
color: rgba(var(--quaUser), 1);
|
||||
border-color: rgba(var(--quaUser), 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide-out panel: collapsed by default; opens to span ~viewport - 3rem.
|
||||
#id_buddy_panel {
|
||||
position: fixed;
|
||||
bottom: 0.5rem; // align bottom edge w. buddy btn
|
||||
left: 1.5rem;
|
||||
right: 1.5rem;
|
||||
height: 3rem; // match buddy btn height for vertical-centre alignment
|
||||
z-index: 317;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
// Closed state — collapse leftward into the buddy btn
|
||||
transform-origin: left center;
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.2s ease-out, opacity 0.15s ease;
|
||||
opacity: 0;
|
||||
|
||||
@media (orientation: landscape) {
|
||||
left: calc(4rem + 0.5rem); // clear the navbar sidebar
|
||||
right: calc(4rem + 0.5rem); // clear the footer sidebar
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (min-width: 1800px) {
|
||||
left: calc(8rem + 0.5rem);
|
||||
right: calc(8rem + 0.5rem);
|
||||
}
|
||||
|
||||
#id_recipient {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
// Generous left padding so the buddy btn glyph (3rem circle pinned
|
||||
// at left:1.5rem) doesn't visually overlap the placeholder/typed text.
|
||||
padding: 0 1rem 0 3.5rem;
|
||||
background-color: rgba(var(--priUser), 1);
|
||||
color: rgba(var(--secUser), 1);
|
||||
border: 0.1rem solid rgba(var(--secUser), 0.5);
|
||||
border-radius: 1.5rem;
|
||||
font-family: inherit;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: rgba(var(--terUser), 0.75);
|
||||
box-shadow: 0 0 0.75rem rgba(var(--terUser), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.btn.btn-confirm {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// html.buddy-open: slide the panel out, fade the kit btn away.
|
||||
html.buddy-open {
|
||||
#id_buddy_panel {
|
||||
transform: scaleX(1);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#id_kit_btn {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Kit dialog open: hide the buddy btn. We don't add an `html.kit-open`
|
||||
// class (game-kit.js uses [open] on the dialog + .active on the btn), so
|
||||
// the mutual-exclusion is driven by `:has()` against the open dialog.
|
||||
html:has(#id_kit_bag_dialog[open]) #id_buddy_btn {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
@import 'note';
|
||||
@import 'tooltips';
|
||||
@import 'game-kit';
|
||||
@import 'buddy';
|
||||
@import 'wallet-tokens';
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user