Both fixtures anchored their "this week" event at a hardcoded 18:00 on the
last day of the current week -- fine any other day, but once the suite runs
past 18:00 on that day itself, the anchor is already in the past and the
view's own start__gte=now filter correctly excludes it, failing the test
for a reason that has nothing to do with the grouping logic being tested.
Falls back to "a few minutes from now" (still always this week) whenever
the fixed anchor would already be behind now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A "yes" to this game doesn't say how reliably a player actually shows up --
each row on the line-up screen now shows this season's turnout rate
(events.services.attendance.player_attendance_rankings, one query for the
whole team), color-coded, omitted for anyone with too little history to
mean anything.
Also: editing a published line-up and hitting "Save" never notified anyone
(it only ever wrote LineupSelection, never touched Attendance.status) --
"Publish"/"Publish changes" now stays reachable after the first publish,
and publish_lineup diffs against who was SELECTED before this run so only
players whose status actually changed get notified, not everyone currently
selected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The bare native file input (browser-chrome "Choose Files" button) clashed
with every other styled field on the screen -- new .m-file-input component
class mirrors management's own .file-input treatment, recolored for the
mobile palette. Also "Linked to {team}" reads clearer than "Posted for
{team}" now that the field is a hard-locked link, not a free-text audience.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The post-composer screen was cramped and text-only, and its "audience"
picker exposed the same "which team(s)" choice the create-event screen
already dropped -- hard-lock teams to the coach's active team instead
(hidden field, required=True so a tampered empty submission can't
silently become club-wide), matching how CoachCreateEventView.teams
already works. Photos reuse management's own NewsPhotoUploadForm/
NewsPhoto machinery, first upload becomes the main picture.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Competition ID (Event.external_game_id) sits under Competition, game-only,
one-off only (no EventSeries equivalent) -- was already part of EventForm,
just never rendered here.
- Gathering time (Event.gathering / EventSeries.gathering_offset) is
available for every kind, in both the one-off and recurring paths -- the
recurring form had this explicitly cut earlier; un-cut now that it's
wanted, mirroring the existing Answers-close/deadline_minutes_before
pattern right next to it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sun no longer strands itself alone on a third line -- Mon-Thu on the first
row, Fri-Sun on the second.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extracted the grouping algorithm (previously duplicated between mobile.views.
CalendarView and management.views.EventListView's own "List" mode) into a
shared events.services.calendar.agenda_groups, and wired it into both --
plus mobile.coach_views.CoachScheduleView, the coach app's own upcoming-
events screen, which had no grouping at all before (one flat card). All
three now read identically and can't drift apart.
Also: give each management event-list row's kind badge a fixed width, so
titles start at the same horizontal position regardless of whether the row
says "Game" or "Tournament".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Team carries no real age-group field, so this is a guess -- the club's own
team with the closest smaller "U<N>" number in its name/short name (if
either side has one at all), current-season roster only. Silently adds
nothing extra for a club that doesn't name teams that way, same as the
existing "last season's roster" half of Suggested already does for a team
with no prior season on file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- teams is now hard-locked to the active team (a hidden field, not a
picker) -- there's no "which team" question on a screen already scoped
to one. groups/club_wide are dropped for the same reason (both widen
the audience past a single team). invited_members/excluded_members stay,
re-scoped to sensible pools (add someone off the roster; exclude someone
on it) instead of "every club member", with a note that a genuinely
multi-team event still needs the desktop.
- Opponent and competition are now rendered (game-only) -- opponent via a
standalone picker with its own "+ New" popup, competition via EventForm's
existing club-agnostic Competition list.
- "+ New location"/"+ New opponent" popups create a Location/Opponent
scoped to the club without leaving the screen: the modal's own htmx
request creates the row and hands back the picker pre-selected via an
out-of-band swap, so the rest of the in-progress form is never touched.
- A "This repeats" toggle switches the same screen to build an EventSeries
instead of a single Event (frequency/interval/weekdays/duration/until),
reusing EventSeriesForm and generate_occurrences the same way
management.views.EventSeriesCreateView does -- including that view's own
behaviour of not sending a per-occurrence notification (the bulk
send_deadline_reminders sweep covers it instead, same as a rolling-
horizon extension).
- Confirmed (and covered with a test) that a single event created this way
still fires notify_new_event, notifying whoever's freshly invited.
Along the way, found and fixed a real, previously-undetected rendering bug
this surfaced: swapping a multi-choice field's widget to CheckboxSelectMultiple
*after* setting its queryset/choices silently drops what the queryset/choices
setter had already pushed onto the old widget, rendering an empty checkbox
list. Hit this for invited_members/excluded_members and weekdays here, and
found the same pre-existing bug in CoachCreateNewsView's own team checkboxes
(the New Post screen's team picker has been rendering empty) -- fixed all of
them (widget swapped in before queryset/choices, not after), with rendering
(not just queryset) regression tests for each.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- The kind-tile grid, title/date/location card, Who card, and Answers-close
card were direct children of the form with no gap between them at all --
the wrapping flex/gap-4 on coach-sheet's own content div only ever applied
between the header bar and the form, not what's inside it. The form itself
is now flex flex-col gap-4.
- The picker offered Practice/Game/Other, none of which actually changed
which fields render. Reworked to Practice/Game/Tournament/Meeting (2x2) --
the four kinds worth adding from the app; social/other stay desktop-only.
Also narrowed the form's own kind choices to match server-side, not just
cosmetically in the template.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- management member_detail.html's Attendance card now shows the same
No-shows count as the mobile player sheet, and the card's content (sparkline
+ tiles) fills the card's height/width instead of sitting in the top-left
corner -- same flex-1/justify-center + stretched-tiles fix used there.
- Coach mode's Attendance and Line-up screens (opening an event) now
highlight the Schedule tab instead of Today -- that's the calendar-icon
tab, and where a coach reaches either from CoachScheduleView's own rows.
- The tab bar's "+" button: rotates 45° into an "×" while its popup is open
(icon reused via CSS transform, no icon swap needed), the popup sits a
little higher above the button (mb-3 -> mb-5), and a dark backdrop now
covers the content behind it so the popup stands out.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
To answer the question this was built to address: "Absent" only ever reads
the RSVP (status=absent) -- someone who said they were coming but never
turned up still counts as present there, since present/absent don't touch
showed_up at all. member_attendance_counts now also returns no_shows, same
present/selected + showed_up=False definition team_no_shows already uses,
so that case shows up as its own number instead of being invisible.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- CoachAddPlayerView/CoachAddStaffView now set active_tab = "coach_squad"
instead of "coach_today" -- both are reached from Squad's own "Add"
buttons, so that's the tab that should stay highlighted while there.
- Squad screen: each staff row (other than your own) gets a remove button,
mirroring the roster's own. Self-removal stays a desktop-only action
(management.views.TeamStaffRemoveView) -- doing it from here would strand
a coach off a team they're actively viewing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"Suggested" is whoever was on this team's roster last season -- now spelled
out in a caption under the chips (same for "No team"), not just left for a
coach to guess at. The search box (?q=, ANDed with whichever filter chip is
active) narrows the eligible pool by first/last name, useful once a club's
pool of eligible members outgrows a single screenful.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On-demand version of send_deadline_reminders' own NO_RESPONSE nudge, same
title/body shape, for a coach who doesn't want to wait for that once-a-day
sweep. Shown only when there's someone silent to remind, and only to whoever
manages the team.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Previously the "Tonight"/"Next up" card and the missing-line-up check flipped
to the next session the instant the current one started (start__gte=now).
Now an event stays current until 30 minutes past its end time, or 90 minutes
past its start when no end is set (most training events carry none) -- a
coach mid-practice or mid-game no longer sees the card jump ahead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Silent and declined are both left out of the default "Responded" view, but
only silent had its own chip to review -- declined now gets one too. The
silent-row background tint is gone; nothing else on this screen tints rows,
and the chip itself already says which bucket you're looking at.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Attendance sheet: "All"/"Goalies" chips replaced with "Responded" (the
new default -- present/selected/maybe) and "Silent"; goalies are just
another player for a practice, and neither silent nor declined members
are expected to show up, so the default view skips both.
- Today's KPI header gains an "Out" tile alongside Squad/In/Silent.
- Squad screen: each roster row now opens a per-player detail sheet with
season attendance stats, tap-to-call buttons (the player's own phone/
emergency phone, plus each guardian's for a child), and -- for whoever
manages the team -- the position/jersey/captaincy edit and a remove-
from-roster action that used to be desktop-only.
- Staff section gets its own "Add" entry point, mirroring the roster's
bulk-add flow with a shared position picker (StaffAssignment.position
is required, unlike a roster spot's).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Coach line-up screen: more breathing room above the sheet, brighter event
subtitle, and the "Schedule" button now matches the date input's height.
- Positions and referee levels can now be deleted from Settings (blocked with
a friendly message if still in use on a roster/referee profile/inheritance
chain).
- The Evaluations nav placeholder is now gated on the formbuilder flag, same
as Forms itself, since the design reuses formbuilder underneath.
- Control panel: each scheduled platform job can now be paused/resumed
individually (features.models.JobToggle), independent of the platform-wide
Maintenance lock.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Publish now still works exactly as before (default action), but a coach
can also pick a future date/time -- events.services.lineup.
schedule_lineup_publish sets Lineup.scheduled_publish_at, and a new
periodic task (events.tasks.publish_scheduled_lineups, every 15 minutes)
publishes it once that time arrives via the same publish_lineup used for a
manual publish, so selection/Attendance/notifications all work identically
either way. A pending schedule can be cancelled or published early from the
same screen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The session card only rendered when something was scheduled today -- a
coach with nothing until Thursday saw an empty screen even though there was
a real next event to show. It now shows whenever there's any upcoming
session, labelled "Tonight" or "Next up · <date>" accordingly, and the KPI
tiles get a header naming which session they're counting.
"Needs you" also now checks every one of the team's next few upcoming games
for a missing line-up, not just whichever happens to be the very next
session -- a practice landing before Saturday's game no longer hides that
the game's own line-up still needs building. Each entry links straight to
its own game's line-up screen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Matches the plain calendar row exactly now (no icon there either) -- the
referee accent colour is the only thing that should set it apart, not a
different shape. Also truncates the title to one line instead of wrapping,
same as every other row's own title.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
A member excluded from an event's Attendance sync by an open onboarding
requirement (events.services.attendance.effective_members) previously just
never saw that event anywhere -- no row, no explanation. Two new read-side
functions mirror that exclusion instead of hiding it: club.services.
onboarding.open_requirements_blocking (per-member, "why") and events.
services.attendance.blocked_upcoming_events_for_member (which of their
upcoming events are affected).
The Calendar now shows those events as a distinct muted "Blocked" row
naming the outstanding requirement, and the event detail page shows a
"Can't sign up yet" card for the same reason -- no RSVP buttons, no lineup/
referee actions, just the explanation. Write-side blocking (who actually
gets an Attendance row, who a coach can select) is untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Accept/Decline now lives only on the event detail page's own "Refereeing"
card (same signup) -- the calendar row is just a link to the event, same
interaction model as every other row on M3's Calendar, instead of carrying
its own inline buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Referee sign-up (Calendar row and event detail's own card) was scoped to
self.me only; a referee-eligible child is exactly as real as a referee-
eligible parent, and a parent signing one up is no different from
answering an RSVP on their behalf -- both surfaces and the respond view now
cover every managed person, and the calendar row names whose invite it is
once there's more than one managed person to tell apart. Event detail's
single-card layout became a per-person list, same shape as "Your answers".
Also hide the scrollbar on both mobile shells (the member shell's own
document scroll, and the coach shell's still-inner-scrolling .coach-sheet).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The app shell now scrolls as one page (body/document) instead of boxing
content inside an inner overflow-auto <main> under a fixed header/tab bar --
header and tab bar are sticky instead, which reads as a single intuitive
scroll (most noticeable on Calendar's long agenda) and, as a side effect,
means htmx's boosted-navigation scroll-to-top actually resets what the user
sees. The inner "This week"/"Today" sticky sub-headers are no longer sticky
themselves, since they'd otherwise collide with the now-sticky app header.
Event detail also shows the same referee Accept/Decline card the Calendar
row offers, for a signed-in account with a pending or accepted sign-up on
that game -- not just on the Calendar. Both the calendar row's and the
event page's Accept/Decline forms are now htmx-boosted (no hx-boost="false")
since neither is Alpine-toggled, so a response no longer forces a full page
reload.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
"1 hour, 20 minutes ago" ran long and ate into the body text's width now
that it's no longer truncated -- a compact two-line HH:MM/date column
(same shape as the calendar row's own day/date marker) fixes that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Creating (or re-teaming) a home game for a club-managed team now auto-
invites every eligible referee (teams.RefereeProfile) via a new
RefereeSignup model, notifying them the same way news/events already do.
They see it as its own distinct row on the mobile Calendar (own accent
colour) and can accept or decline right there -- accepting routes through
the existing capacity-checked assign_referee (assigned_by=None marks it
self-service), so it lands as a real EventReferee row with no separate sync
step. The desktop referee-management screen and event detail page both
surface pending invites and flag self-signed-up referees distinctly from
admin assignments.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Coach mode's line-up screen no longer has lines/slots -- just a yes/no
toggle per available roster player, grouped by their roster position, both
in coach mode and on the published member-side view. Replaces LineupUnit/
LineupSlot with a single LineupSelection model.
Notifications now show their full body text (no more truncatechars) and the
unread colour bar spans the full row height via self-stretch, matching the
calendar row's own marker, so it still reads correctly once a body wraps to
several lines.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
A family with several kids on a news item's audience now gets one email/one
Notification, not one per child -- deduped on resolved recipient emails, so
overlapping (not just identical) guardian sets still collapse correctly.
Event notifications are untouched: each child still needs their own reply.
Home's "Club news" teaser is also decoupled from the person-scope switcher --
a parent with no team of their own still sees their kids' team news when
they've picked their own "Me" chip, not just when "All" is selected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Once a game's line-up is published, "Your answers" switches to a read-only
status pill and a new Line-up card shows every unit/slot; a SELECTED member
can still report they can no longer make it, which flips them to Absent and
notifies the team's managers immediately (the closed-deadline guard doesn't
apply here, since a published line-up is usually well past it). Also shows
the full date for the "Meet" time, not just the hour.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
flex-1 + min-w-0 still wasn't producing equal-width In/Out (and In/Maybe/
Out) buttons in practice -- a longer label's own content could still win
a wider share. Switched every such row to CSS grid (grid-cols-2/3): each
track is a content-independent minmax(0, 1fr), so equal width is
guaranteed by the grid itself rather than relying on flex-basis/min-width
interactions. Applied to the hero In/Out row, its Cancel/Confirm pair,
event_detail's 3-way In/Maybe/Out row, and its own Cancel/Confirm pair.
Also brightened the hero's face-off/meet/location meta line (was
text-on-dark, a dim grey) to plain white, matching the readability pass
already done on the "are you in?" subtext and the button labels.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The "are you in?" subtext and the In/Out button labels were both dim
grey (text-on-dark-dim/text-on-dark) against a photo backdrop -- bumped
to white, and gave both buttons a shared border (border-white/30) for
definition against the image, same colour on both so they read as a
matched pair.
The Out reason step (label, textarea, Cancel/Confirm) used to be loose
floating controls directly on the photo -- now wrapped in one bordered/
backed panel (same border colour as the In/Out pair) so it reads as one
cohesive control instead of scattered text and buttons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The In/Out hero buttons (and Out's reason confirm) stopped working: Alpine
owns the toggle between a row's two sibling forms (buttons vs. the reason
prompt), and hx-boost="true" on <body> had htmx *also* intercepting the
same submit -- both ended up fighting over it. Fixed by marking every
write-action <form> across the mobile app hx-boost="false" (link
navigation, where the smooth-navigation feature actually matters, is
untouched). The one exception worth calling out: coach/lineup.html's form
uses three submit buttons sharing one <form> via formaction overrides --
htmx's boost reads the form's own action rather than the submitter's
formaction override, so a boosted click there would always have posted to
the wrong endpoint regardless of the Alpine conflict.
Also:
- A reason for Out is now mandatory, not just captured -- empty and
punctuation-only "answers" (a bare ".", "-", "??") are rejected
server-side (the authoritative check) with textarea required/minlength
as a client-side nudge on top.
- Maybe can now carry an optional reason too, visible to the same audience
as Out's (this member/family, and Coach mode's bench attendance) -- one
shared reason form in event_detail.html's per-person row, its hidden
status input following whichever of Maybe/Out was tapped.
- The 3-way In/Maybe/Out row (and the 2-way hero In/Out) now use min-w-0 on
every button so flex-1 actually splits the row evenly -- a longer
label's own intrinsic width was winning it a bigger share otherwise.
- HomeView's "Needs your answer" list now excludes events whose
registration deadline has already passed -- replying is no longer
possible there (same rule EventDetailView.post already enforces), so it
doesn't belong in a "still needs a reply" list. hero_attendance is
unaffected -- it always shows the true next event, falling back to a
read-only pill once its own deadline closes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Attendance.note already existed but nothing wrote to it. Picking "Out"
anywhere (Home's hero, Coach Today's "Also yours", event_detail's per-
person answers) now opens an optional reason field before submitting,
via an Alpine two-step within the same form rather than a separate
confirmation screen. EventDetailView.post stores it only when the status
is actually "absent", and clears it the moment someone flips back to
In/Maybe -- a stale "sick" note under a since-changed answer would just
be confusing.
Private by construction, not by a permission check: nothing anywhere
renders another member's own note -- the squad-response view stays
counts-only like it already was. The only two places that read it back
are this member/family's own "Your answers" card and Coach mode's bench
attendance screen, both already scoped to people the viewer has a real
claim on.
Also, on the shared hero In/Out buttons (Home + Coach Today): dropped the
green "In" styling -- nobody's answered yet at that point, so a green
default misleadingly read as an already-recorded answer -- and added
min-w-0 to both buttons so flex-1 actually splits the row evenly; a
longer label's own intrinsic width was winning it a bigger share
otherwise.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
A bright/light event photo (e.g. a well-lit rink) let too much through at
the top and middle of the gradient, making the "Next up" eyebrow and the
face-off/location meta line hard to read against it. Increases the
gradient stops on both Home's hero card and Event detail's header --
darkening the backdrop rather than lightening the text, which was already
white/near-max.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The previous turn's all-ice-blue header wasn't what the design doc actually
called for -- its own text is explicit: "the role switcher with Coach
active (bg-ice)", i.e. the accent belongs on the switcher's active segment,
not the header background. Reverted .coach-header to dark ink (matching
Member mode's own navy) and added .role-switcher-item-active-ice (bg-ice/
ice-ink) for just the Manager pill, leaving Member mode's own white/ink
active state untouched. All the header_extra text this touched (Attendance,
Line-up) reverts to white/on-dark to match.
Fixed the switcher jumping position between modes: both shells' top info
row now carries the same min-h-11 (previously only implicit in the member
header, via its bell button's own height), and the switcher sits directly
below that row in both -- the coach header's team-picker pill row (multi-
team accounts) now renders *after* the switcher instead of before it, so
its conditional presence can't shift the switcher down anymore.
Tab bar order swapped per feedback -- Today / Squad / Schedule / [+],
not Today / Squad / [+] / Schedule -- the "+" reads better as the last,
most prominent item than sandwiched in the middle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Reworks the coach shell per feedback that the previous dark-ink header
didn't read as visually distinct from Member mode's own navy header, and
that the rounded "sheet overlaps header" treatment wasn't landing:
- .coach-header is now solid ice-blue (--color-ice, never club-themed --
Coach mode's own signature colour) with --color-ice-ink foreground text,
instead of dark ink with white text. Every header_extra block across the
coach templates (attendance, lineup) is recoloured to match.
- .coach-sheet drops the 20px rounded-top/negative-margin overlap -- flush
edge-to-edge below the header now, same join the member shell already uses.
- The header's "Head coach" label was hardcoded regardless of the account's
actual role -- CoachScopeMixin now resolves active_team_role from the
person's own current-season StaffAssignment.position on the active team,
so a team manager or physio sees their real title, not someone else's.
Tab bar is now Today / Squad / [+] / Schedule -- no Me tab (the account's
own settings already live in Member mode via the role switcher, no need
for a second one). Squad (CoachSquadView) is a new roster+staff screen for
the active team; Schedule (CoachScheduleView) is a new full upcoming-events
list for the team, each row routed straight to the coach-relevant action
(Attendance for a practice, Line-up for a game) rather than the Member-
shell RSVP page. The "+" is a raised ice-blue circle opening a small popup
with New event/New post/Add player -- three genuinely different actions,
so the button opens a menu rather than committing to one destination.
Today's own inline New event/New post/Add player buttons are gone now that
the tab bar covers the same ground.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Every icon in mobile/templates/mobile/ was a hand-rolled inline <svg> with
raw path data -- management and controlpanel have used django-lucide
throughout instead (confirmed already 100% consistent there, audited as
part of this change). django-lucide is a pure server-side Python tag with
no JS/CDN runtime dependency, so this is a drop-in swap producing the same
kind of literal <svg><path .../></svg> markup mobile already wrote by hand,
just pulled from the shared vendored icon set instead of duplicated per
call site. Mapped each shape to its nearest Lucide name: bell, house
("home" doesn't exist as a lucide name -- confirmed by hand), calendar,
newspaper, user, users, chevron-left, chevron-right, check, x. Hardcoded
hex strokes (#0b1220, #fff) became stroke="currentColor" + a text-ink/
text-white class, matching how every other icon in this app already gets
its color.
Also caught and fixed a hx-boost gap while touching coach/base.html: the
coach tab bar's own "Me" link (-> mobile:me, a Member-shell view) was
missing the hx-boost="false" every other cross-shell link already got.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
hx-boost="true" on both shells' <body> turns every same-shell link/form
into an AJAX navigation (swapping <body>'s children, pushing the URL)
instead of a full browser reload -- htmx.js was already loaded on every
page but had zero actual usage anywhere in the codebase until now.
Only <body>'s children swap under a boost, never the tag itself, so
anything that crosses between the two differently-styled shells (Member's
bg-paper vs Coach's bg-ink, different data attributes) is marked
hx-boost="false" to force a real navigation instead: both role-switcher
links, the Me page's "Teams I coach/manage" row (leads into Coach mode),
and Coach Today's "Also yours" quick-RSVP forms (post to the Member-shell
event_detail view). The calendar-sync "Add to calendar" webcal:// link is
also excluded -- it's meant to hand off to the OS calendar app, not be
treated as in-app navigation.
Also, on the Me page:
- Renamed the mode switcher and the "Teams I coach" card to "Manager"/
"Teams I coach/manage" -- a team manager assigned there isn't
necessarily a coach.
- Fixed the team badge, which was slicing short_name to 2 characters
("U16" showing as "U1") -- replaced with a plain icon instead of text,
so there's nothing left to truncate (the full team name is still the
row's own label).
- Styled the whole card dark (m-card-dark), matching the design mock's
own dark "Coach mode" promo card treatment for this part of M5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
New beyond the design mock: a "Teams I coach" card listing every
current-season StaffAssignment self.me holds (team + role), each row
linking straight into Coach mode for that team via ?team=<pk>, which
CoachScopeMixin already resolves and persists to the session. Shown for
any staffed team, not just ones self.me manages -- Coach mode's own
screens already render read-only for a non-management position, so
there's nothing to hide on this summary row.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
New models (events/models.py): Lineup (one per event), LineupUnit (Line 1,
Defence pair 1, ... -- coach-entered labels, no fixed sport structure since
neither Club nor Team carries one), LineupSlot (one position, optionally
filled by a member). events/services/lineup.py's publish_lineup is the
reason Attendance.AttendanceStatus.SELECTED/NOT_SELECTED existed at all --
publishing flips every slotted member to SELECTED and every other available
(non-out, non-silent) roster member to NOT_SELECTED, then notifies only the
selected players.
Placement is a native <select> per slot, batch-saved with one "Save
line-up" submit, not the design mock's drag-and-drop -- this codebase has
no established htmx interaction pattern yet (htmx.js is loaded but nothing
uses it) to build a live per-tap version on, and a reliable plain form beats
a first, unproven real-time interaction for an already-large screen.
place_member handles "swap" semantics for it: placing a member vacates any
other slot of theirs in the same lineup, bumping whoever was already in the
target slot back to the available pool.
Wires the missing pieces from C1's own docstring: the "needs you" line-up-
not-published row and the tonight card's "Line-up" button, both deferred
in that stage specifically because this model didn't exist yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
management.forms.TeamMembershipForm is shaped for one member at a time
(per-row jersey number/position/captain flags), which doesn't fit a "tap a
few names, add them" flow -- the design mock itself shows plain checkboxes,
no inline position picker. Reuses the same two eligibility rules the form
applies internally (teams.services.eligible_roster_members, minus whoever's
already on this team+season) directly instead, and lets a coach fill in
jersey number/position afterward on the desktop -- the model's own
help_text already documents a blank position as a normal, expected state.
"Suggested" (on this team last season) is real, computed data via
club.models.Season.before. "Age eligible" from the mock isn't built --
neither Club nor Team carries an age-group field to compare a birth date
against, so faking that filter would just mean it silently matched nothing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
management.forms.NewsForm defaults its teams field to every club team --
fine for an editor/admin, but a real gap for a coach, who should only ever
post as their own team, never "on behalf of" one they don't run. Re-scoped
to teams_managed_by via self.managed_teams, and made required (a coach's
post is always team-scoped, never empty/club-wide -- that stays an
editor/admin claim).
Gated with club.services.access.can_add_news, which already includes
is_coach_manager. On submit the post goes straight to
News.submit_for_review() plus the same notify_editors_of_pending_review
call the desktop's own NewsSubmitForReviewView makes, landing in an
editor's queue instead of a silent draft. The button reads "Send" rather
than the mock's "Publish" -- can_publish_news stays editor/admin-only, so
that's the honest description of what actually happens.
visibility is left at the model's own INTERNAL default rather than
building the mock's "also on club website" toggle -- an editor reviewing
the pending post can widen it before publishing if a public-site
placement is actually warranted; that's a real gate, not a decorative
row, so it isn't reproduced as one here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
management.forms.EventForm already scopes its teams field to
teams_managed_by(user, club) via EventAudienceFormMixin -- exactly the
restriction a coach needs, so it's reused as-is rather than duplicated.
Only a subset of the desktop form's fields is rendered (title, kind, teams,
location, start, answers-close deadline); the rest stays unset, all of it
optional on the model except max_referees, which is dropped from the form
entirely so its own default (2) applies instead of demanding a value this
screen has no field for.
After a successful save, replicates management.views.EventCreateView's own
notify_new_event.delay(...) call by hand, since this isn't a CreateView --
attendance sync itself is automatic via events/signals.py.
"Repeat weekly" from the design mock isn't wired up this stage -- the
recurring-series machinery is a separate form with its own fields; adding
it is later work, not something to fake with an inert toggle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
First slice of the coach-mode build (design_handoff_rosterchief_platform/
README.md's "Coach mode" section, C1-C6). Ships the foundation together with
the two screens the design doc calls out as the reason coaches install
anything at all, rather than landing a Today screen with a dead "Check
attendance" button:
- mobile/coach_mixins.py's CoachScopeMixin -- the dark-mode mirror of
PersonScopeMixin, scoped by active team (via club.services.access'
teams_staffed_by/teams_managed_by) instead of managed people.
- A standalone dark ink/ice shell (mobile/templates/mobile/coach/base.html)
with the mode's signature 20px-radius overlapping sheet, reusing the
--color-ice/--color-ink tokens that already existed in assets/mobile.css
but were unconsumed until now.
- The Coach/Member role switcher is re-added to the member shell, gated on
a real staff assignment (has_coach_access), replacing the "deliberately
not rendered yet" placeholder.
- C1 Today: stat tiles, a tonight's-session card, a silent-players "needs
you" row, and an "Also yours" card reusing HomeView's own hero-RSVP
pattern scoped to the coach's own member record.
- C2 Bench attendance: writes through events.services.attendance.
record_check_in, which existed for exactly this and had no caller yet.
Read-only for staff on a team without a management position.
Line-up (C3), create event (C4), post news (C5), and add-to-roster (C6)
follow in later stages -- see the coach-mode plan.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Home already had the M1 dues card; Me's own "Payments & dues" row was
explicitly stubbed out with nowhere to lead. Give it a real destination:
a Payments screen (open balances for everyone managed, reusing Home's
dues-card layout via a shared _dues_row.html partial) and a "N OPEN"
pill on the Me row itself, only shown once something is actually owed.
club.services.fees.open_dues_rows is factored out so Home and Payments
can never drift apart on what counts as "still open".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The agenda used to hard-cut at a ~14-day lookahead with no way to see
anything past it. Drop the cutoff and bucket whatever falls beyond
"Next week" into per-month groups (each its own sticky header), so the
screen stays a full upcoming agenda instead of losing events that were
still worth seeing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9