- 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
Several overflow-x-auto wrappers (table cards, a text preview) got an
accidental *vertical* scrollbar too -- per the CSS overflow spec, setting
only overflow-x to a non-visible value forces the other axis to compute as
auto if left unset, so any of these taller than the viewport were trapped
scrolling independently of the page (Safari showed it plainly; other
browsers hid it more subtly). Fixed everywhere with overflow-y-visible,
except the week calendar.
The week grid genuinely needs to stay its own bounded, contained scroll:
it always spans the full 24h day (never clipped, by design), so folding it
into the page's own scroll would mean scrolling past a screenful of empty
early hours most weeks. Instead it's capped to 70vh with a real
overflow-y-auto, and a small script scrolls it to just before the week's
first event on load (events.services.calendar.week_grid now reports
first_event_hour) -- no more landing on an empty view by default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Same fix as mobile/templates/mobile/base.html earlier this session: sidebar
and topbar are sticky instead of the page being a fixed-height shell around
an inner overflow-y-auto <main> -- that read as two separate scrollable
areas nested inside each other, most noticeable on a tall page like the
week calendar (event_list.html's own .cal-week-body, routinely taller than
a screenful of hours).
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
New §5.8: a thin PlayerEvaluation/EvaluationSettings envelope around
formbuilder's already-built Form/Field/Submission/Answer (submission =
evaluator, envelope = who it was about + team/season), club-wide EVALUATOR
role independent of MEMBER_ADMIN, player profile combining evaluation
history with existing attendance/games-per-team data. Never visible to the
evaluated player or their guardians.
Also corrects formbuilder's status (models/services are built, just no
submit UI yet -- not "planned" as this doc previously had it) and moves the
referee self-service item from "still open" to resolved, since it shipped
this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Under Settings, next to Referee levels/Forms -- but deliberately not behind
a waffle Flag like the shop/forms stubs, since a flag-gated link disappears
from the nav entirely until someone remembers to turn it on, which defeats
the point of a standing reminder. Visible to MEMBER_ADMIN/ADMIN
unconditionally, links to a bare "coming soon" page (reuses
_generic_list.html with an empty object_list -- there's no model yet).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Joining a team or group already re-synced Attendance rows for its upcoming
events (events/signals.py), but did so silently. A fresh membership (not a
field edit, not a removal) now sends one summary notification -- "N new
events on your calendar" -- rather than one per event, so joining mid-season
with a whole recurring series already scheduled doesn't flood the member
with pushes.
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
KPIs and the chart now share a single flex row (chart card wider, flex-[3])
instead of a full KPI row followed by a tall standalone chart card -- the
per-referee fee breakdown moves into the chart's own tooltip instead of a
separate list underneath, so this stays compact and the actual
games-needing-a-referee list isn't pushed below the fold.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
The season-at-a-glance numbers become their own full-width KPI row (same
treatment as the existing "games in view" row above), with the chart and
per-referee breakdown as a separate full-width card below, instead of
squeezed into a narrow sidebar next to the chart.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Games refereed per referee this season, as a chart.js bar chart plus a
small table with fees paid per referee, alongside season-at-a-glance KPIs
(active referees, total games refereed, average per referee) -- separate
from the existing "games in view" KPIs, which track the upcoming range
picked by the filter strip rather than season-to-date workload.
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
divide-y was stacking two borders: the custom .divide-y > * + * rule (top,
--color-rule) plus Tailwind's own generated divide-y utility (bottom width/
style with no color, so it fell back to a dark currentColor). Give each
staff row its own border-b border-rule instead, the same single-border
approach the roster table already uses via .table tbody tr.
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
Fixed at h-9.5 it matched the old two-line layout, but fell short next
to a three-line row (title + meta + location). self-stretch keeps it
matching whatever the content column actually renders.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Games previously got a 4px left border while training/other events used a
3px bar next to the date — move games onto the same bar for one consistent
marker position. Also break the location name out of the crowded meta line
into its own row so it doesn't get truncated away.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Legal address (street/zip/city) split out of the "Club" card into its
own "Legal address" card. "Colours" moved to the second column, above
"Live preview" -- the card it actually drives, and now sits next to
instead of scrolled away from it.
The colour inputs are physically outside <form id="club-settings-form">
now, so each gets an explicit form="club-settings-form" attribute to
keep submitting with the rest of the identity form -- HTML supports this
natively, no JS or structural change needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9