Skip to content

feat: season swap checklist - #279

Open
TheBlackSide243 wants to merge 3 commits into
Anyesh:mainfrom
TheBlackSide243:pr/season-swap
Open

TheBlackSide243 wants to merge 3 commits into
Anyesh:mainfrom
TheBlackSide243:pr/season-swap

Conversation

@TheBlackSide243

@TheBlackSide243 TheBlackSide243 commented Oct 18, 2026 •

Copy link
Copy Markdown

Description

Adds a Season swap page: what to put away and what to bring out for the next two months, based on
each item's seasons (hemisphere-aware from the user's location) and, for items without seasons,
on what was worn in the same weeks last year. Items worn in the last three weeks stay out. The
user ticks items off as they go (remembered in the browser).

  • GET /api/v1/season-swap, no new tables (services/season_swap.py).
  • Frontend page /dashboard/season, nav entry, translations for all 8 locales.

This is one of a set of small, independent PRs (each branches from main and can be reviewed or
dropped on its own); they only overlap in the nav/router registration lines.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or build changes

Checklist

  • I have read the CONTRIBUTING guide
  • My code follows the project's coding style
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally
  • I have updated documentation as needed
  • My changes don't introduce new warnings or errors

Testing

  • Backend: ruff check, ruff format --check, migration heads check and the full pytest suite on top of current main (2031 passed).
  • Frontend: npm run lint, i18n:check, vocab:check, tsc --noEmit and vitest (693 passed).
  • Running on my own self-hosted instance (Docker Compose on TrueNAS, Ollama).

Test Environment

  • Docker Compose
  • Kubernetes
  • Local development

🤖 Generated with Claude Code

Summary by Sourcery

Add a location-aware seasonal wardrobe swap checklist for organizing items between seasons.

New Features:

  • Add a season-swap API and dashboard page that recommend items to put away or bring out based on location-aware seasons and prior-year wear history.
  • Allow users to mark swap items complete with browser-persisted checklist state and cross-tab updates.
  • Add season-swap navigation and localized interface text across all supported locales.

Enhancements:

  • Keep recently worn garments available regardless of seasonal recommendations and report items that remain in use.

Tests:

  • Add service and endpoint coverage for seasonal recommendations, hemisphere handling, recent wear exclusions, history-based suggestions, and API responses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Adds an authenticated season-swap API and service that produces hemisphere-aware wardrobe recommendations using item tags and prior-year wear history, plus a localized dashboard checklist whose completion state is persisted per season transition in localStorage.

Sequence diagram for the season swap checklist

sequenceDiagram
    actor User
    participant Page as SeasonSwapPage
    participant Hook as useSeasonSwap
    participant API as GET /api/v1/season-swap
    participant Service as SeasonSwapService
    participant DB as Database

    User->>Page: Open /dashboard/season
    Page->>Hook: useSeasonSwap()
    Hook->>API: GET /season-swap
    API->>Service: plan(current_user, today)
    Service->>DB: Load ready, non-archived clothing items
    Service->>DB: Load prior-year ItemHistory
    Service-->>API: SwapPlan
    API-->>Hook: SwapResponse
    Hook-->>Page: Render put-away and bring-out items
    User->>Page: Toggle item checklist
    Page->>Page: localStorage.setItem(DONE_KEY)
Loading

Flow diagram for season swap recommendation logic

flowchart TD
    A[Load eligible wardrobe items] --> B[Calculate current and upcoming seasons]
    B --> C{Worn in last 21 days?}
    C -->|Yes| D[Keep item out]
    C -->|No| E{Item has season tags?}
    E -->|No| F{Worn in matching weeks last year?}
    F -->|Yes| G[Bring out: last_year]
    F -->|No| D
    E -->|Yes| H{Tags match current or upcoming season?}
    H -->|Upcoming only| I[Bring out: season]
    H -->|Neither| J[Put away: season]
    H -->|Current or all-season| D
Loading

File-Level Changes

Change Details Files
Adds backend season-swap planning that combines season tags, hemisphere-aware season calculation, recent-wear exclusions, and prior-year wear history.
  • Calculates current and 60-day upcoming seasons from the user's latitude.
  • Classifies ready, non-archived items into put-away, bring-out, or stays lists.
  • Uses a 21-day recent-wear window to keep recently used items out.
  • Uses wear history from a date window around the same period last year for untagged items.
  • Exposes the plan through an authenticated GET endpoint with typed response models.
  • Adds unit and endpoint coverage for seasonal, hemisphere, recency, history, and API behavior.
backend/app/api/router.py
backend/app/api/season_swap.py
backend/app/services/season_swap.py
backend/tests/test_season_swap.py
Introduces the dashboard season-swap page with interactive checklist state persisted in browser storage.
  • Fetches season-swap data through a React Query hook and shared query key.
  • Displays current-to-upcoming season context and separate put-away and bring-out item columns.
  • Allows item completion toggles and scopes persisted checklist state to the season transition.
  • Handles loading, error, empty-list, translated labels, item images, and accessibility state.
frontend/app/dashboard/season/page.tsx
frontend/lib/hooks/use-season-swap.ts
frontend/lib/hooks/query-keys.ts
Integrates the new page into dashboard navigation and localization.
  • Adds desktop and mobile navigation links with a calendar icon.
  • Registers the season translation namespace.
  • Adds navigation and season-page translations for all eight supported locales.
frontend/components/sidebar.tsx
frontend/components/mobile-sidebar.tsx
frontend/lib/i18n/locales.ts
frontend/messages/de/nav.json
frontend/messages/de/season.json
frontend/messages/en/nav.json
frontend/messages/en/season.json
frontend/messages/fr/nav.json
frontend/messages/fr/season.json
frontend/messages/it/nav.json
frontend/messages/it/season.json
frontend/messages/ja/nav.json
frontend/messages/ja/season.json
frontend/messages/ko/nav.json
frontend/messages/ko/season.json
frontend/messages/zh-CN/nav.json
frontend/messages/zh-CN/season.json
frontend/messages/zh-TW/nav.json
frontend/messages/zh-TW/season.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added javascript Pull requests that update javascript code python Pull requests that update python code labels Oct 18, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 8 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="frontend/lib/hooks/use-season-swap.ts" line_range="27" />
<code_context>
+  const { status } = useSession();
+  useSetTokenIfAvailable();
+  return useQuery({
+    queryKey: queryKeys.seasonSwap,
+    queryFn: () => api.get<SeasonSwap>('/season-swap'),
+    enabled: status === 'authenticated',
</code_context>
<issue_to_address>
**Previous account's wardrobe appears**

When the authenticated account changes while the previous account's season-swap result remains fresh in the same app, `queryKeys.seasonSwap` is shared across accounts, so TanStack Query reuses the previous user's result without immediately running `queryFn`; while refetching is pending or fails, the new account sees the previous user's clothing and image URLs.

Include the authenticated user in the season-swap query key so each account has a separate cache entry.

Also at `frontend/lib/hooks/query-keys.ts:26`.
</issue_to_address>

### Comment 2
<location path="backend/app/services/season_swap.py" line_range="22" />
<code_context>
+LOOK_AHEAD_DAYS = 60
+# Last year's wears inside this window around the same date mark an untagged item as seasonal.
+HISTORY_WINDOW = (timedelta(days=15), timedelta(days=105))
+STILL_IN_USE_DAYS = 21
+ALL_SEASONS = {"all-season", "spring", "summer", "fall", "winter"}
+
</code_context>
<issue_to_address>
**Untagged items get wrong recommendations**

When an untagged item's wear history falls in the corresponding weeks last year, or only in the current 15–105-day interval, `SeasonSwapService.plan` builds `start` and `end` from `HISTORY_WINDOW` as 15–105 days after the corresponding date, so it misses wears from the matching weeks and counts unrelated later wears; `plan_swap` then gives users incorrect bring-out recommendations.

Set `HISTORY_WINDOW` and the resulting query bounds to cover the corresponding weeks last year rather than days 15–105 after the date.

Also at `backend/app/services/season_swap.py:21`, `backend/app/services/season_swap.py:88-89`.
</issue_to_address>

### Comment 3
<location path="frontend/lib/hooks/use-season-swap.ts" line_range="27" />
<code_context>
+  const { status } = useSession();
+  useSetTokenIfAvailable();
+  return useQuery({
+    queryKey: queryKeys.seasonSwap,
+    queryFn: () => api.get<SeasonSwap>('/season-swap'),
+    enabled: status === 'authenticated',
</code_context>
<issue_to_address>
**Checklist plan stays stale**

When an item or wear mutation succeeds and the cached season-swap result is revisited before it becomes stale, item and wear mutations invalidate their own queries but not `seasonSwap`, so React Query serves the old plan; users see items in the wrong checklist section or miss recent-wear exceptions until a refetch.

Invalidate the season-swap query when item or wear mutations succeed.
</issue_to_address>

### Comment 4
<location path="frontend/app/dashboard/season/page.tsx" line_range="108" />
<code_context>
+  const t = useTranslations('season');
+  const seasonLabel = useSeasonLabel();
+  const { data, isLoading, isError } = useSeasonSwap();
+  const key = data ? `${data.current_season}-${data.upcoming_season}` : '';
+  const [done, setDone] = useState<Set<string>>(new Set());
+
</code_context>
<issue_to_address>
**Last year's checklist stays checked**

When the same current/upcoming season pair recurs in a later year and the prior item IDs are still on the checklist, `key` contains only the season names, so `readDone` accepts the prior year's saved IDs and displays those items as completed in the new swap.

Include the year or season-cycle identity in the saved checklist key.
</issue_to_address>

### Comment 5
<location path="frontend/app/dashboard/season/page.tsx" line_range="116-121" />
<code_context>
+  }, [key]);
+
+  const toggle = (id: string) =>
+    setDone((current) => {
+      const next = new Set(current);
+      if (next.has(id)) next.delete(id);
+      else next.add(id);
+      try {
+        window.localStorage.setItem(DONE_KEY, JSON.stringify({ season: key, ids: Array.from(next) }));
+      } catch {
+        // Not remembered in private mode; the list still works for this visit.
</code_context>
<issue_to_address>
**Checklist changes disappear across tabs**

When two tabs toggle different checklist items before either tab sees the other's update, each tab writes its entire in-memory ID set to the same localStorage key, so the later write overwrites the earlier tab's change and completed items appear unchecked after reload.

Merge each tab's changes with the latest saved IDs or synchronize tab state before writing.
</issue_to_address>

### Comment 6
<location path="frontend/app/dashboard/season/page.tsx" line_range="19" />
<code_context>
+import { useSeasonSwap, type SwapEntry } from '@/lib/hooks/use-season-swap';
+import { useSeasonLabel, useTypeLabel } from '@/lib/hooks/use-translated-constants';
+
+const DONE_KEY = 'wardrowbe.seasonSwapDone';
+
+function readDone(season: string): Set<string> {
</code_context>
<issue_to_address>
**Accounts overwrite saved progress**

When multiple accounts use the same browser and both update their checklist for the same season pair, the single localStorage record is shared by every account in the browser, so when one account saves its checked IDs it replaces another account's progress for the same season pair.

Scope the localStorage key or stored record by authenticated user ID.

Also at `frontend/app/dashboard/season/page.tsx:121`.
</issue_to_address>

### Comment 7
<location path="backend/tests/test_season_swap.py" line_range="69" />
<code_context>
+        response = await client.get("/api/v1/season-swap", headers=auth_headers)
+        assert response.status_code == 200, response.json()
+        data = response.json()
+        assert {"current_season", "upcoming_season", "put_away", "bring_out", "stays"} <= set(data)
</code_context>
<issue_to_address>
**Empty checklists pass endpoint test**

When the endpoint returns empty `put_away` and `bring_out` lists despite the committed item, `test_endpoint` checks only the status and response keys at lines 67-69, so an endpoint that returns empty checklist arrays despite the committed summer item passes and the item is absent from the user's checklist.

Assert that the committed sandals item is present in the appropriate checklist entry.
</issue_to_address>

### Comment 8
<location path="backend/tests/test_season_swap.py" line_range="45" />
<code_context>
+
+    def test_recently_worn_items_stay_out(self):
+        shorts = _item("shorts", ["summer"], last_worn_at=OCTOBER - timedelta(days=3))
+        assert plan_swap([shorts], OCTOBER, 45.7, set()).put_away == []
+
+    def test_untagged_items_follow_last_year(self):
</code_context>
<issue_to_address>
**Recently worn items can disappear**

When `plan_swap` drops a recently worn item from all plan lists, `test_recently_worn_items_stay_out` checks only that `put_away` is empty at line 45, so a regression that drops `shorts` from every plan list still passes even though the item does not stay out.

Assert that the recently worn item remains represented in the plan, such as by checking `stays` or its expected list membership.
</issue_to_address>

Sourcery assessment

Approval pending. 8 findings to address first.

Blocking findings: frontend/lib/hooks/use-season-swap.ts:27, backend/app/services/season_swap.py:22, frontend/lib/hooks/use-season-swap.ts:27, frontend/app/dashboard/season/page.tsx:108, frontend/app/dashboard/season/page.tsx:121, and 3 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread frontend/lib/hooks/use-season-swap.ts Outdated
const { status } = useSession();
useSetTokenIfAvailable();
return useQuery({
queryKey: queryKeys.seasonSwap,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical · Previous account's wardrobe appears

When the authenticated account changes while the previous account's season-swap result remains fresh in the same app, queryKeys.seasonSwap is shared across accounts, so TanStack Query reuses the previous user's result without immediately running queryFn; while refetching is pending or fails, the new account sees the previous user's clothing and image URLs.

Include the authenticated user in the season-swap query key so each account has a separate cache entry.

Also at frontend/lib/hooks/query-keys.ts:26.

Prompt for AI agents
In `frontend/lib/hooks/use-season-swap.ts` at line 27:

**Previous account's wardrobe appears**

When the authenticated account changes while the previous account's season-swap result remains fresh in the same app, `queryKeys.seasonSwap` is shared across accounts, so TanStack Query reuses the previous user's result without immediately running `queryFn`; while refetching is pending or fails, the new account sees the previous user's clothing and image URLs.

Include the authenticated user in the season-swap query key so each account has a separate cache entry.

Also at `frontend/lib/hooks/query-keys.ts:26`.

LOOK_AHEAD_DAYS = 60
# Last year's wears inside this window around the same date mark an untagged item as seasonal.
HISTORY_WINDOW = (timedelta(days=15), timedelta(days=105))
STILL_IN_USE_DAYS = 21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Untagged items get wrong recommendations

When an untagged item's wear history falls in the corresponding weeks last year, or only in the current 15–105-day interval, SeasonSwapService.plan builds start and end from HISTORY_WINDOW as 15–105 days after the corresponding date, so it misses wears from the matching weeks and counts unrelated later wears; plan_swap then gives users incorrect bring-out recommendations.

Set HISTORY_WINDOW and the resulting query bounds to cover the corresponding weeks last year rather than days 15–105 after the date.

Also at backend/app/services/season_swap.py:21, backend/app/services/season_swap.py:88-89.

Prompt for AI agents
In `backend/app/services/season_swap.py` at line 22:

**Untagged items get wrong recommendations**

When an untagged item's wear history falls in the corresponding weeks last year, or only in the current 15–105-day interval, `SeasonSwapService.plan` builds `start` and `end` from `HISTORY_WINDOW` as 15–105 days after the corresponding date, so it misses wears from the matching weeks and counts unrelated later wears; `plan_swap` then gives users incorrect bring-out recommendations.

Set `HISTORY_WINDOW` and the resulting query bounds to cover the corresponding weeks last year rather than days 15–105 after the date.

Also at `backend/app/services/season_swap.py:21`, `backend/app/services/season_swap.py:88-89`.

Comment thread frontend/lib/hooks/use-season-swap.ts Outdated
const { status } = useSession();
useSetTokenIfAvailable();
return useQuery({
queryKey: queryKeys.seasonSwap,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Checklist plan stays stale

When an item or wear mutation succeeds and the cached season-swap result is revisited before it becomes stale, item and wear mutations invalidate their own queries but not seasonSwap, so React Query serves the old plan; users see items in the wrong checklist section or miss recent-wear exceptions until a refetch.

Invalidate the season-swap query when item or wear mutations succeed.

Prompt for AI agents
In `frontend/lib/hooks/use-season-swap.ts` at line 27:

**Checklist plan stays stale**

When an item or wear mutation succeeds and the cached season-swap result is revisited before it becomes stale, item and wear mutations invalidate their own queries but not `seasonSwap`, so React Query serves the old plan; users see items in the wrong checklist section or miss recent-wear exceptions until a refetch.

Invalidate the season-swap query when item or wear mutations succeed.

Comment thread frontend/app/dashboard/season/page.tsx Outdated
const t = useTranslations('season');
const seasonLabel = useSeasonLabel();
const { data, isLoading, isError } = useSeasonSwap();
const key = data ? `${data.current_season}-${data.upcoming_season}` : '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Last year's checklist stays checked

When the same current/upcoming season pair recurs in a later year and the prior item IDs are still on the checklist, key contains only the season names, so readDone accepts the prior year's saved IDs and displays those items as completed in the new swap.

Include the year or season-cycle identity in the saved checklist key.

Prompt for AI agents
In `frontend/app/dashboard/season/page.tsx` at line 108:

**Last year's checklist stays checked**

When the same current/upcoming season pair recurs in a later year and the prior item IDs are still on the checklist, `key` contains only the season names, so `readDone` accepts the prior year's saved IDs and displays those items as completed in the new swap.

Include the year or season-cycle identity in the saved checklist key.

Comment thread frontend/app/dashboard/season/page.tsx Outdated
Comment on lines +116 to +121
setDone((current) => {
const next = new Set(current);
if (next.has(id)) next.delete(id);
else next.add(id);
try {
window.localStorage.setItem(DONE_KEY, JSON.stringify({ season: key, ids: Array.from(next) }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Checklist changes disappear across tabs

When two tabs toggle different checklist items before either tab sees the other's update, each tab writes its entire in-memory ID set to the same localStorage key, so the later write overwrites the earlier tab's change and completed items appear unchecked after reload.

Merge each tab's changes with the latest saved IDs or synchronize tab state before writing.

Prompt for AI agents
In `frontend/app/dashboard/season/page.tsx` at lines 116-121:

**Checklist changes disappear across tabs**

When two tabs toggle different checklist items before either tab sees the other's update, each tab writes its entire in-memory ID set to the same localStorage key, so the later write overwrites the earlier tab's change and completed items appear unchecked after reload.

Merge each tab's changes with the latest saved IDs or synchronize tab state before writing.

Comment thread frontend/app/dashboard/season/page.tsx Outdated
import { useSeasonSwap, type SwapEntry } from '@/lib/hooks/use-season-swap';
import { useSeasonLabel, useTypeLabel } from '@/lib/hooks/use-translated-constants';

const DONE_KEY = 'wardrowbe.seasonSwapDone';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Accounts overwrite saved progress

When multiple accounts use the same browser and both update their checklist for the same season pair, the single localStorage record is shared by every account in the browser, so when one account saves its checked IDs it replaces another account's progress for the same season pair.

Scope the localStorage key or stored record by authenticated user ID.

Also at frontend/app/dashboard/season/page.tsx:121.

Prompt for AI agents
In `frontend/app/dashboard/season/page.tsx` at line 19:

**Accounts overwrite saved progress**

When multiple accounts use the same browser and both update their checklist for the same season pair, the single localStorage record is shared by every account in the browser, so when one account saves its checked IDs it replaces another account's progress for the same season pair.

Scope the localStorage key or stored record by authenticated user ID.

Also at `frontend/app/dashboard/season/page.tsx:121`.

Comment thread backend/tests/test_season_swap.py Outdated
response = await client.get("/api/v1/season-swap", headers=auth_headers)
assert response.status_code == 200, response.json()
data = response.json()
assert {"current_season", "upcoming_season", "put_away", "bring_out", "stays"} <= set(data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Empty checklists pass endpoint test

When the endpoint returns empty put_away and bring_out lists despite the committed item, test_endpoint checks only the status and response keys at lines 67-69, so an endpoint that returns empty checklist arrays despite the committed summer item passes and the item is absent from the user's checklist.

Assert that the committed sandals item is present in the appropriate checklist entry.

Prompt for AI agents
In `backend/tests/test_season_swap.py` at line 69:

**Empty checklists pass endpoint test**

When the endpoint returns empty `put_away` and `bring_out` lists despite the committed item, `test_endpoint` checks only the status and response keys at lines 67-69, so an endpoint that returns empty checklist arrays despite the committed summer item passes and the item is absent from the user's checklist.

Assert that the committed sandals item is present in the appropriate checklist entry.

Comment thread backend/tests/test_season_swap.py Outdated

def test_recently_worn_items_stay_out(self):
shorts = _item("shorts", ["summer"], last_worn_at=OCTOBER - timedelta(days=3))
assert plan_swap([shorts], OCTOBER, 45.7, set()).put_away == []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Recently worn items can disappear

When plan_swap drops a recently worn item from all plan lists, test_recently_worn_items_stay_out checks only that put_away is empty at line 45, so a regression that drops shorts from every plan list still passes even though the item does not stay out.

Assert that the recently worn item remains represented in the plan, such as by checking stays or its expected list membership.

Prompt for AI agents
In `backend/tests/test_season_swap.py` at line 45:

**Recently worn items can disappear**

When `plan_swap` drops a recently worn item from all plan lists, `test_recently_worn_items_stay_out` checks only that `put_away` is empty at line 45, so a regression that drops `shorts` from every plan list still passes even though the item does not stay out.

Assert that the recently worn item remains represented in the plan, such as by checking `stays` or its expected list membership.

TheBlackSide243 and others added 2 commits October 18, 2026 22:21
- The swap query is keyed by account and fetched again on every visit, so
  another sign-in never sees this one's clothes and recent wears count.
- Ticks are stored per account and per swap year, and each toggle merges
  with what other tabs saved (they also pick up each other's changes).
- The history window comment says what it covers: the coming season as it
  was a year ago.
- Tests pin the date and check where items land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants