Commit Graph

683 Commits

Author SHA1 Message Date
Claude 428edbcfe8 Remove bare-value fallback from _extract_labeled
The JS always emits {id, label} objects now; the else branch was dead code
and the docstring was wrong. Update the remaining test that was still
passing bare strings.

https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
Claude 11cd62a3b9 Introduce LabeledOption and RangeValues named types
Replace all tuple[str, str] annotations with purpose-specific names:
- LabeledOption = tuple[str, str] for (value, label) pairs used in
  FilterChoice, FilterSelect params, _modifier_options, _find_label,
  and _extract_labeled.
- RangeValues(min, max) NamedTuple for _parse_range return values,
  making the two fields self-documenting at every call site.

Export LabeledOption from common.components alongside SearchSelectOption.
Document the "name compound types explicitly" convention in CLAUDE.md.

https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
Claude d9902146dc Clean up label-embedding architecture
- Move {id,label} stripping into _SetCriterion.from_json() so both
  MultiCriterion and ChoiceCriterion normalise at the parse boundary;
  the querying layer stays typed (list[int] / list[str]) and clean.
- Revert MultiCriterion to a thin _extra_q() override; _SetCriterion.to_q()
  is no longer duplicated.
- JS: readSearchSelect always emits {id, label} objects — no conditional
  mixed-type arrays. filter_bar.js stores them as-is for all fields,
  removing the fragile isIdField hardcoded list.
- Update tests to use the {id, label} filter format.

https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
Claude 83cbac9505 Update uv.lock
https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
Claude 0285243172 Embed labels in filter criteria (Stash-style) to retire pill resolver
Store {id, label} objects instead of bare IDs in MultiCriterion value/excludes.
FilterSelect pills now render directly from the embedded labels — no DB round-trip
to _resolve_game/device/platform_options. The filter URL and saved presets are
self-describing. MultiCriterion.to_q() extracts ids for querying; bare ints are
still accepted for backward compatibility.

Closes #9

https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
Claude 112d3107ef Consolidate Multi/Choice criteria into a shared _SetCriterion base
MultiCriterion and ChoiceCriterion were near-duplicate copies whose INCLUDES
branches had drifted — the exclude-only bug existed in one but not the other.
Extract the shared include/exclude/null set-membership logic into a _SetCriterion
base implemented once (INCLUDES with empty-list guards, EQUALS as an alias,
IS_NULL/NOT_NULL); subclasses contribute only their value type and their own
modifiers via _extra_q (INCLUDES_ALL for Multi; EXCLUDES/NOT_EQUALS for Choice).
Behaviour preserved (full modifier vocabulary kept); the duplication that caused
the drift is gone. Surfacing the modifier axis and harmonizing EXCLUDES is
tracked in #10.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 22d7834ae9 Fix exclude-only multi filters matching nothing
MultiCriterion.to_q (used by SessionFilter for game/device) unconditionally added
field__in=value even when value was empty, and __in=[] matches no rows — so a
filter with only excludes (e.g. device excludes 11, no game/device includes)
returned zero results. Guard the empty value like ChoiceCriterion already does,
so an exclude-only criterion means 'all rows except the excluded ids'.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
lukas 60773e7755 Fix variable names 2026-06-08 19:12:28 +02:00
Claude 79fa4bef44 Use element primitives instead of inline Component; add Template primitive
Add a Template() primitive for the standard <template> tag and export it. Replace
inline Component(tag_name="div"/"span"/"input"/"template") in search_select.py
and Pill with Div/Span/Input/Template; drop the private _template helper in favour
of Template at the call sites. Bare custom-styled <button>s stay on Component
(the opinionated Button() would inject unwanted classes). Document the
prefer-primitives convention in CLAUDE.md.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 15bb3ce1b9 Expand the ss namespace prefix to search-select everywhere
Spell out the abbreviated data-ss-* hook attributes (data-search-select-option,
-label, -mode, -template, -action, -type, -modifier, -modifier-option, -pills,
-search, -options, -no-results) and the JS expando properties (_searchSelectInit,
_searchSelectLabel, _searchSelectDirty, _searchSelectOption) across components,
JS, and tests — no abbreviations left in the widget's hooks.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude a06e772e42 Rename data-ss-tpl attribute to data-ss-template
Spell out the abbreviation in the template marker attribute too, matching the
complete-words convention applied to the variables.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 29b42e0f3d Use complete words for variable names; document the convention
Rename abbreviated identifiers in the PR's code to full words: tpl→template,
e→event, el→element, removeBtn→removeButton, and single-letter loop variables
(o→option, g/d/p→game/device/platform, v→value/modifier_value). Add a
'name variables with complete words' convention to CLAUDE.md.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude f210f818a9 Single-source combobox markup via <template> cloning
Eliminate the Python/JS class-string duplication: the server renders hidden
<template> prototypes (row, pill, include/exclude/modifier pills) using the same
component functions, and search_select.js clones them, filling only the
[data-ss-label] slot, value, and data-* attrs. All Tailwind class strings and DOM
structure now live solely in the Python components — the JS no longer hardcodes
any class. Pill gains an opt-in label_slot; the shell takes a templates list.

Companion issue #8 tracks the further HTMX-idiomatic step of returning rendered
row HTML from the search endpoint.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
lukas 6bc7da9f2f Fix button visibility on select option hover 2026-06-08 19:12:28 +02:00
lukas c9189b9f8e Update allowed builders for pnpm 2026-06-08 19:12:28 +02:00
lukas a37257f9c8 Update uv.lock security 2026-06-08 19:12:28 +02:00
Claude db047dfaf2 Fix dropdown overlapping the search box
Anchor the options panel with top-full. As an absolutely positioned child of the
now-flex field container, its static position was centered by items-center,
placing the dropdown over the search box instead of below it.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 6aff12b7b2 Address PR review: combobox field layout and dark-mode contrast
- Wire the long-defined-but-unused _FIELD_CLASS into the container so pills and
  the search input form a single padded flex row; the flex-1 input now fills the
  widget instead of looking unclickable inside a larger box (affects both
  SearchSelect and FilterSelect via the shared shell).
- Filter option labels get text-body so they're readable on dark backgrounds.
- Filter +/- buttons get text-body (readable at rest) and hover:border-brand-strong
  so the border stays visible against the brand hover fill.
- Mirror the filter class changes in search_select.js and rebuild base.css.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 12b0b0af61 Remove the bespoke SelectableFilter widget
FilterSelect fully replaces it: delete SelectableFilter and its _selectable_*
helpers, the now-unused _get_filter_options, selectable_filter.js, and the .sf-*
rules in input.css (rebuilt base.css). The three list views load search_select.js
instead of selectable_filter.js. Drop the SelectableFilter export and refresh
docs/comments that referenced it.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 1a206d719b Migrate filter bars to FilterSelect
Replace the bespoke SelectableFilter in all three bars with FilterSelect: enum
fields (status, type, ownership) pre-render their fixed options; model-backed
fields (game(s), platform, device) use the search endpoints with prefetch and
resolve only the selected ids to pill labels — dropping the per-page queries that
fetched every game/platform/device. filter_bar.js now reads filter-mode
SearchSelect widgets via readSearchSelect (data-included/excluded/modifier),
preserving the {value, excludes, modifier} JSON and id Number() coercion; the
redundant session game/device blocks are gone. Drop FilterBar's now-unused
platform_options param. Rebuild base.css for the inline filter-pill utilities and
update the bar tests to the new markup.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude a6532807cb Wire filter-mode behavior into search_select.js
Dispatch on data-ss-mode: in filter mode, value rows (server-rendered or fetched
via buildRow) carry +/- buttons that add include/exclude pills, and pinned
modifier pseudo-options set a lone, mutually-exclusive modifier pill. Pill removal
handles the modifier flag; filter pills carry no hidden inputs. Extend
readSearchSelect to serialise filter widgets into data-included / data-excluded /
data-modifier (the shape the filter bar consumes), leaving form widgets'
data-values path unchanged. JS class strings mirror the FilterSelect constants.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude d7e6efa68a Add FilterSelect: include/exclude combobox on the shared shell
FilterSelect renders value rows with +/- (include/exclude) buttons, check/cross
pills for the included/excluded sets, and an optional set of pinned modifier
pseudo-options (e.g. (Any)/(None)) that stay visible above the value rows. A
selected modifier is mutually exclusive with value pills. It delegates assembly
to _combobox_shell and supports both pre-rendered options (complete set) and
search_url + prefetch (windowed); included/excluded are passed as resolved
value+label so pills show labels even outside the fetched window. Styling is
inline (ported from the old SelectableFilter CSS) so nothing lives in input.css.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 003e6ebe15 Add prefetch + instant-local/debounced-remote search to combobox
Introduce a general 'prefetch' option (rows to load on first open, default 0 =
unchanged) carried as data-prefetch. Rework the JS search so a search_url widget
filters its loaded window instantly on every keystroke while issuing a debounced
server request for the rest, with an AbortController so a slower earlier response
can never overwrite a newer one. No-results stays hidden until the server
response decides it, avoiding a flash over an incomplete window. On first focus a
prefetch-enabled widget seeds its window immediately. Rename single-letter locals
to full words while reworking these functions.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude e2cbd4a9f4 Extract _combobox_shell from SearchSelect
Pull the domain-agnostic combobox skeleton (pills region, search box, options
panel with its no-results node, outer container) into a private _combobox_shell
helper. SearchSelect now builds its form-specific pills and option rows and
delegates assembly to the shell. Rendered markup is byte-identical; a structural
test guards the fixed region order so future builders (e.g. a filter variant)
can share the shell without drift.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 547894d8d0 Fix SearchSelect not showing selected label in single-select search box
Django CI/CD / test (push) Successful in 45s
Django CI/CD / build-and-push (push) Successful in 1m19s
The search_value was computed but never applied as a value attribute
on the search input element.

https://claude.ai/code/session_01BZHdra2YBPwS3umwsGrgUj
2026-06-07 20:52:56 +02:00
Claude 061b5e6d8a Fix add_session prefilling game from last session
When no game_id is provided, the session form should start with no game
selected rather than defaulting to the last session's game.

https://claude.ai/code/session_01BZHdra2YBPwS3umwsGrgUj
2026-06-07 20:52:56 +02:00
lukas 05e30610e9 Fix typo from merge
Django CI/CD / test (push) Failing after 44s
Django CI/CD / build-and-push (push) Has been skipped
2026-06-07 20:32:00 +02:00
lukas 0aa87a17fe Merge branch 'main' of github.com:KucharczykL/timetracker
Django CI/CD / test (push) Failing after 13m50s
Django CI/CD / build-and-push (push) Has been skipped
2026-06-07 20:22:50 +02:00
Claude 7c2c08501e Adopt SearchSelect for device, platform, and play event game fields
- Parameterize SearchSelectWidget with a required options_resolver so
  each widget explicitly names its resolver instead of implicitly using
  _game_options
- Add autofocus support: SearchSelect forwards it to the search input,
  and SearchSelectWidget extracts it from Django's attrs dict
- Add _device_options and _platform_options resolvers (single pk__in
  queries, same pattern as _game_options)
- Add /api/devices/search and /api/platforms/search endpoints
- Switch PlayEventForm.game from plain Select to SearchSelectWidget
  (preserving autofocus), and use SingleGameChoiceField for correct labels
- Switch SessionForm.device to SearchSelectWidget
- Switch PurchaseForm.platform and GameForm.platform to SearchSelectWidget
- Wire ModuleScript("search_select.js") into add/edit playevent and
  add/edit game views

https://claude.ai/code/session_013fpJD54HxRgxRv2xzwXGNo
2026-06-07 20:20:43 +02:00
lukas d3b29ff1d4 Merge pull request #3 from KucharczykL/claude/claude-md-docs-Yn7bE 2026-06-07 15:08:58 +02:00
Claude 1c17fbcb6d Update CLAUDE.md with current codebase state
Reflects the migration to pure-Python components, the new filter/criteria
architecture, FilterPreset model, stats split into data/content modules,
filter_presets views, layout.py render_page() pattern, and frontend stack.

https://claude.ai/code/session_01Nj9HbTK5LMVBYH6N741JMv
2026-06-07 12:32:15 +00:00
lukas 3b9c05d674 Improve year picker on stats page
Django CI/CD / test (push) Successful in 45s
Django CI/CD / build-and-push (push) Successful in 1m16s
2026-06-07 10:48:32 +02:00
lukas 2c2827df47 Merge branch 'main' of ssh://192.168.0.106:2022/lukas/timetracker
Django CI/CD / test (push) Successful in 51s
Django CI/CD / build-and-push (push) Successful in 1m24s
2026-06-07 09:01:23 +02:00
lukas a6384fc003 Improve search select 2026-06-07 09:01:18 +02:00
lukas 7f5384de48 Merge pull request #2 from KucharczykL/dependabot/uv/pytest-9.0.3
Django CI/CD / test (push) Successful in 57s
Django CI/CD / build-and-push (push) Successful in 1m19s
Bump pytest from 8.4.2 to 9.0.3
2026-06-07 07:41:35 +02:00
dependabot[bot] ffcc4ba0f3 Bump pytest from 8.4.2 to 9.0.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.2 to 9.0.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.4.2...9.0.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.0.3
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-07 05:40:56 +00:00
lukas 7493f6fc28 Update Django et al
Django CI/CD / test (push) Successful in 53s
Django CI/CD / build-and-push (push) Successful in 1m41s
2026-06-07 07:36:10 +02:00
lukas f9b91c5900 update uv.lock
Django CI/CD / test (push) Successful in 50s
Django CI/CD / build-and-push (push) Successful in 1m31s
2026-06-07 07:31:28 +02:00
lukas 36098374c2 move stuff to docs 2026-06-07 07:31:28 +02:00
lukas afc16aabbb Implement search select component
Django CI/CD / test (push) Successful in 40s
Django CI/CD / build-and-push (push) Successful in 1m24s
2026-06-06 22:52:26 +02:00
lukas 3ce3356064 Refine filters 2026-06-06 19:37:14 +02:00
lukas ed8589a972 Fix more code smells
Django CI/CD / test (push) Successful in 39s
Django CI/CD / build-and-push (push) Successful in 1m19s
2026-06-06 13:14:55 +02:00
lukas f4161bf3f4 Improve stats code smells 2026-06-06 12:19:15 +02:00
lukas b6864e59ce Add filters
Django CI/CD / test (push) Successful in 43s
Django CI/CD / build-and-push (push) Successful in 1m22s
2026-06-06 12:13:04 +02:00
lukas 36b1382015 Fix code smells 2026-06-06 08:15:19 +02:00
lukas d101aecd70 Move from HTML templates to pure Python
Remove cruft
2026-06-06 07:51:10 +02:00
lukas 09db54e940 Add new lint and format checks 2026-06-06 07:10:54 +02:00
lukas f090643026 Keep moving towards pure Python components 2026-06-02 22:35:11 +02:00
lukas ec1828b823 Migrate cotton to Python + template tag shims
Django CI/CD / test (push) Successful in 32s
Django CI/CD / build-and-push (push) Successful in 1m22s
2026-06-02 22:19:55 +02:00
lukas 94c3d9050a Fix make init 2026-06-02 16:32:26 +02:00