Two small Checkpoint-1 polish items from Konrad:
(1) 'The projects and team dropdowns open empty, and only after you
click in the text box do the options appear.'
Choices.js's default open state has the dropdown closed (is-active
absent) — the user normally has to click the search input to reveal
options. But a pill click clearly means 'show me the list,' so we
now call `showDropdown(true)` on the Choices instance right after
the popover opens. Deferred via setTimeout(0) so it runs AFTER any
cross-filter destroy/recreate has settled in the same tick.
(2) 'In the date selection I accidentally saw there is text that is way
too dark below the From date selector — "Leave blank for a single
month".'
The inline style was `opacity: 0.75` on top of Bootstrap's default
`.form-text` colour (inherits --bs-secondary-color — very dark on
our dark theme). Replaced with `color: var(--text-tertiary)` + full
opacity so the hint is readable in both dark and light themes.
This matches CLAUDE.md's convention of always using theme tokens
for text that should sit in the 'hint' legibility band.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Konrad's Checkpoint-1 feedback:
'Inside the all time projects table, can we have a column with the
last transaction date for a project? It will make it easier to find
data for projects. It is nice to have the filter, but you can still
skip around looking for when the last transaction was.'
Added a 'last_activity' entry to each alltime_projects row in
_build_report_context — computed as max(WorkLog.date) grouped by
project name (respects the same project_ids/team_ids filters already
applied to all_time_logs). Rendered in both the on-screen report
(report.html) and the PDF (report_pdf.html) as a new 'Last Activity'
column sitting between 'Start' and 'Working Days'.
Existing ChapterOneEnrichmentTests extended with a last_activity
assertion locking in the 'most recent log date' semantics.
No other tests touched. 47/47 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the plan at docs/plans/2026-04-23-inline-filters-plan.md Task 5, the
now-redundant configuration modal goes away:
core/templates/core/_report_config_modal.html → deleted (160 lines)
core/templates/core/index.html:
- Dashboard 'Generate Report' tile → plain link to
/report/?from_month={% now 'Y-m' %}&to_month={% now 'Y-m' %} so the
click lands on the report page with the current month pre-filled.
- Modal {% include %} at EOF removed.
core/templates/core/report.html:
- Both 'New Report' buttons (header + bottom action bar) deleted;
comments updated to say the pills ARE the new-report interface.
- {% include 'core/_report_config_modal.html' %} removed.
- Stale 'Task 5 will delete...' comment on the Choices.js CDN block
updated.
Konrad's exact ask (Checkpoint 1 feedback):
'Does it make sense to have this popup window for reports? Don't you
think clicking on generate report should just default to current month
and open the report page where users can adjust report filters?'
→ Yes. The pills do exactly that, one click in.
Verification:
grep -rn 'reportConfigModal\|_report_config_modal' core/ returns 0 hits.
47/47 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Checkpoint-1 polish (Konrad):
- 'From (optional)' — the parenthetical is now ~50% smaller (0.6rem)
so the label's primary text dominates, matching his intent of a
subordinate hint rather than a competing word.
- 'Until' — adds a small info-circle icon with a Bootstrap tooltip
reading 'Single month select'. Inline small-font text was my first
attempt but wrapped to two lines inside the narrow column; the icon
tooltip keeps the label tidy while the hint is one hover away.
Bootstrap tooltip auto-init (base.html) handles the binding —
matches CLAUDE.md's global tooltip pattern.
No functional change. 47/47 tests still green (no view code touched).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Konrad's clarification on the Checkpoint-1 UX revision: the visual order
should follow English reading — "from X until Y" reads left-to-right, so
"From" belongs on the left and "Until" on the right. Previous commit
71f8558 placed Until on the left because it's the always-filled anchor,
but that fights the natural sentence order and was confusing.
Optionality is unchanged:
- Until (right, always filled) = anchor month
- From (left, optional) = blank means single-month report
No JS change needed — input IDs (popoverFromMonth / popoverToMonth) stay
the same; only column positions in the <div class="row"> were swapped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Checkpoint 1 second-round UX feedback (Konrad, 2026-04-23):
(1) "The until option must be auto filled (and used for single month) and
the from date must be optional — this makes more sense and less clicks
if the user wants to eg check the last 3 months."
→ Inverted the month pickers. "Until" is now the always-filled anchor
(defaults to URL to_month, falling back to the current YYYY-MM when no
filter is set). "From (optional)" is the disclosure; blank = single
month (JS submits from_month = to_month). Visual order swapped so
Until sits on the left as the primary action. Matches the admin mental
model: "I want data ending now, maybe going back N months."
(2) "Is it possible to show only teams and projects that has transactions
within the selected dates — filter out teams and projects that has no
log for any of the dates chosen?"
→ The pill pickers AND the cross-filter (project_team_pairs_json) are
now scoped to the current date range. A team/project with zero logs in
the window doesn't clutter the lists. The (project_id, team_id) pair
map follows the same rule — cross-filter disables options that never
paired inside THIS window.
Guarantee: entries that are currently in the URL's ?project= / ?team=
selection are always unioned back in, so the user's own picks can
never disappear from the list even when they'd otherwise be out of
scope (e.g. picking a project, then narrowing the date range to a
period with no logs on that project).
Design-doc note at lines 108-112 of 2026-04-23-inline-filters-design.md
originally said "Scope = entire history" — Konrad's real-usage feedback
overrides that decision. Will be recorded in the Task 6 "Shipped" block.
Tests: two new ones lock in the behaviour —
- test_pickers_and_pairs_are_date_scoped: out-of-range project/team
absent from both the picker lists and the pair map
- test_url_selected_projects_survive_even_out_of_range: URL selection
unioned in regardless of date window
Plus existing 3 tests still green. 47/47 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Checkpoint 1 UX feedback (Konrad, 2026-04-23) surfaced three friction points
that all traced back to the same over-engineered "multi-stage commit" model:
1. When Choices.js opened its dropdown, it covered the popover's OK button.
User had to click in a thin strip "outside the multi-select but inside
the dropdown pane" to close Choices.js before OK became reachable.
2. Changing only a project/team didn't light up the global Apply button
(dirty-state diff bug on multi-selects), and even when it did, clicking
Apply didn't actually update the report tables. Also the Apply button
sat at the far right of the pill strip — easy to miss on desktop.
3. Single-month reports required changing BOTH From and To pickers; for a
low-frequency admin tool, that's a tax on the most common flow.
Instead of patching three bugs, collapsed the entire pending/dirty/Apply
model. Each popover's OK now:
- Rebuilds the URL from its OWN inputs only (keeping other filters intact)
- Navigates → full SSR page reload → report re-renders
The user reads the result of their change immediately; there's no "did I
remember to click Apply?" step.
Side-effect wins:
- 'dirty state', 'pending state', 'updateAllPillsDirty', 'revert...',
cross-filter auto-removal, and the toast system all become unnecessary.
Net -187 lines across template + CSS.
- The bug from (2) self-disappears because there's no dirty-diff step.
- Sticky popover footer (position: sticky; bottom: 0; z-index: 2) pins
OK to the popover edge even when Choices.js expands — solves (1).
- The To month picker is labelled "Until (optional)" with "Leave blank
for a single month" hint. Blank on submit → to_month = from_month.
Single-month URLs round-trip with a blank To input (so the form and
the data agree).
Cross-filter preserved: on popover open, the OTHER pill's URL selection
still disables invalid dropdown options. Just no runtime auto-remove —
unnecessary because the next OK submits and the server takes over.
Tested in the browser via preview MCP:
- All three pills open popovers on click
- Range URL shows both month pickers filled
- Single-month URL shows To blank
- OK with blank To → navigates to from_month=X&to_month=X
- Sticky footer keeps OK in viewport when Choices.js is open
- 45/45 tests still pass (no backend contract change)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Main interactive layer for the inline-filters feature. Appends two
blocks to report.html (inside {% block content %}, before the final
{% endblock %}):
1. Choices.js CDN <link> + <script> (admin-only gated, SRI-hashed) —
moved here because Task 5 will delete _report_config_modal.html,
which previously loaded the CDN. Keeping this on the report page
directly means the pills stay functional after modal retirement.
2. A scoped IIFE that wires up the three filter pills into an
interactive, state-managed UI:
- Click pill -> open popover (lazy-inits Choices.js on first open)
- Click outside / Esc / other pill -> close
- OK commits popover's local edits into pending state; dirty pills
get the orange outline + pulsing dot; Apply button slides in
- Cross-filter: picking projects auto-removes now-invalid teams
with toast notice ("Removed Team X — no logs on selected
projects"), and vice versa. Scope = entire history.
- Apply -> rebuilds querystring from pending state + navigates
(full page reload, same URL scheme as the retired modal)
- Reset -> reverts all pills to URL-current values
XSS-safe throughout: textContent and createElement; no innerHTML with
user data. Matches the pattern in base.html's work-log-payroll modal
from the Work-Log Payroll feature.
Graceful fallback: if Choices.js CDN fails to load, the module bails
early with a console warning; native <select multiple> still works
inside the popovers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the three static filter pills with clickable buttons and
inline popover shells below each one. Popovers remain hidden by
default (hidden attribute) — the JS module in Task 4 will wire up
open/close, dirty state, and Apply behaviour.
Structure per pill:
- .filter-pill-wrap (position-relative container)
- <button class="filter-pill filter-pill--editable" data-filter="...">
with chevron indicating clickability
- <a class="filter-pill__x"> (existing × clear-filter link, preserved)
- .filter-popover (the editable widget — date picker for the Date
pill, Choices.js multi-select for Projects/Teams pills)
Apply + Reset buttons sit in .apply-filters-group at the right end,
initially hidden. A <div id="filter-toast-container"> is pre-placed
for the cross-filter auto-removal notices.
Three json_script blocks embed the data the JS needs:
- projectTeamPairs: (project_id, team_id) pairs for cross-filter
- urlSelectedProjectIds / urlSelectedTeamIds: current URL state for
dirty diffing + reset
No visible behaviour change yet (no CSS, no JS). Page renders same
as before until Tasks 3-4 light it up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final chapter of the executive redesign. Renders the
team_project_activity context as a pivot: rows=teams, columns=projects,
cell=COUNT(DISTINCT work-log dates). Zero cells show em-dashes in muted
grey (not '0') so non-zero cells stand out. Row totals, column totals,
and grand total on the bottom row.
Adds a tiny dictlookup template filter (format_tags.py) — Django
templates can't index a dict by a dynamic variable key, and the pivot
cell lookup is cells_by_project_id[col.id]. Defensive None + TypeError
guards so a malformed context can't 500 the page.
.table-total-row CSS: 2px top border + inset background for the footer
row so totals visually separate from the data rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the numbered 'III' chapter heading above the existing Worker
Breakdown card (the widest table in the report). Promotes the table
to .report-numeric (tabular-nums) for perfect column alignment
across dynamic adjustment columns — Inter's tabular-nums variant
keeps the rand amounts pixel-aligned.
No data or structural changes to the breakdown itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the old narrow four-card All-Time/YTD row (dropped in Task 9)
with two wider cards under a numbered 'Chapter I - Lifetime Context'
heading. Projects card gains Start, Working Days, Total Cost, and
Avg R / Working Day columns per the design. Teams card keeps name +
total.
Adds .chapter-heading and .chapter-num CSS for the orange numbered
markers (I, II, III, IV) and .report-numeric class that applies
tabular-nums across the number columns of every report table.
Renames the existing 'Selected Period' heading to Chapter II.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chapter 0 of the executive redesign: four large cards at the top
showing Paid This Period, Outstanding Now (live, stamped with the
generation time), FoxFitt Avg/Day, and FoxFitt Avg/Month.
Drops the old four-small-cards All-Time/YTD row (YTD specifically
documented as redundant per design doc section 3). All-Time detail
moves into Chapter I in the next task.
New .stat-card--hero variant uses Poppins 1.85rem for the number,
uppercase tracked labels, subtle tertiary sub-lines. tabular-nums
keeps the R-amounts pixel-aligned across cards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three pills under the header: date range, project(s), team(s). Shows
comma-joined names when multi-valued (project_name in context is
already a comma-joined string from Task 6). × buttons on the project
and team pills remove just that filter via a rebuilt querystring;
the calendar pill has no × (date range is required).
Helper context keys query_string_without_project / _without_team do
the rebuild in the view via QueryDict.setlist so multi-value keys
are properly stripped (pop() only removes the first occurrence).
Pill CSS uses existing design tokens (--bg-inset, --accent,
--text-primary, --border-default, --text-tertiary, --bg-card-hover)
so dark and light themes work without overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>