Files
Aniworld/docs/instructions.md
2026-02-20 21:22:16 +01:00

29 KiB
Raw Blame History

Aniworld Web Application Development Instructions

This document provides detailed tasks for AI agents to implement a modern web application for the Aniworld anime download manager. All tasks should follow the coding guidelines specified in the project's copilot instructions.

Project Overview

The goal is to create a FastAPI-based web application that provides a modern interface for the existing Aniworld anime download functionality. The core anime logic should remain in SeriesApp.py while the web layer provides REST API endpoints and a responsive UI.

Architecture Principles

  • Single Responsibility: Each file/class has one clear purpose
  • Dependency Injection: Use FastAPI's dependency system
  • Clean Separation: Web layer calls core logic, never the reverse
  • File Size Limit: Maximum 500 lines per file
  • Type Hints: Use comprehensive type annotations
  • Error Handling: Proper exception handling and logging

Additional Implementation Guidelines

Code Style and Standards

  • Type Hints: Use comprehensive type annotations throughout all modules
  • Docstrings: Follow PEP 257 for function and class documentation
  • Error Handling: Implement custom exception classes with meaningful messages
  • Logging: Use structured logging with appropriate log levels
  • Security: Validate all inputs and sanitize outputs
  • Performance: Use async/await patterns for I/O operations

📞 Escalation

If you encounter:

  • Architecture issues requiring design decisions
  • Tests that conflict with documented requirements
  • Breaking changes needed
  • Unclear requirements or expectations

Document the issue and escalate rather than guessing.


<EFBFBD> Credentials

Admin Login:

  • Username: admin
  • Password: Hallo123!

<EFBFBD>📚 Helpful Commands

# Run all tests
conda run -n AniWorld python -m pytest tests/ -v --tb=short

# Run specific test file
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py -v

# Run specific test class
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py::TestWebSocketService -v

# Run specific test
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py::TestWebSocketService::test_broadcast_download_progress -v

# Run with extra verbosity
conda run -n AniWorld python -m pytest tests/ -vv

# Run with full traceback
conda run -n AniWorld python -m pytest tests/ -v --tb=long

# Run and stop at first failure
conda run -n AniWorld python -m pytest tests/ -v -x

# Run tests matching pattern
conda run -n AniWorld python -m pytest tests/ -v -k "auth"

# Show all print statements
conda run -n AniWorld python -m pytest tests/ -v -s

#Run app
conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload

Implementation Notes

  1. Incremental Development: Implement features incrementally, testing each component thoroughly before moving to the next
  2. Code Review: Review all generated code for adherence to project standards
  3. Documentation: Document all public APIs and complex logic
  4. Testing: Maintain test coverage above 80% for all new code
  5. Performance: Profile and optimize critical paths, especially download and streaming operations
  6. Security: Regular security audits and dependency updates
  7. Monitoring: Implement comprehensive monitoring and alerting
  8. Maintenance: Plan for regular maintenance and updates

Task Completion Checklist

For each task completed:

  • Implementation follows coding standards
  • Unit tests written and passing
  • Integration tests passing
  • Documentation updated
  • Error handling implemented
  • Logging added
  • Security considerations addressed
  • Performance validated
  • Code reviewed
  • Task marked as complete in instructions.md
  • Infrastructure.md updated and other docs
  • Changes committed to git; keep your messages in git short and clear
  • Take the next task

TODO List:

Task 1: Add APScheduler Dependency

  • 1.1 Add APScheduler>=3.10.4 to requirements.txt
  • 1.2 Verify installation: conda run -n AniWorld pip install APScheduler>=3.10.4
  • 1.3 Verify import works: conda run -n AniWorld python -c "from apscheduler.schedulers.asyncio import AsyncIOScheduler; from apscheduler.triggers.cron import CronTrigger; print('OK')"

Task 2: Extend SchedulerConfig Model

File: src/server/models/config.py

The existing SchedulerConfig has enabled: bool and interval_minutes: int. Extend it with cron-based fields.

  • 2.1 Add field schedule_time: str = "03:00" — 24h HH:MM format for the daily run time
  • 2.2 Add field schedule_days: List[str] with default ["mon","tue","wed","thu","fri","sat","sun"] — lowercase 3-letter day abbreviations
  • 2.3 Add field auto_download_after_rescan: bool = False — whether to auto-queue and start downloads of all missing episodes after a scheduled rescan completes
  • 2.4 Add a field_validator for schedule_time that validates HH:MM format (00:0023:59). Raise ValueError with a clear message on invalid input
  • 2.5 Add a field_validator for schedule_days that validates each entry is one of ["mon","tue","wed","thu","fri","sat","sun"]. Raise ValueError on invalid entries. Empty list is allowed (means scheduler won't run)
  • 2.6 Keep interval_minutes field for backward compatibility but it will no longer be used when schedule_time is set. Add a docstring noting this deprecation
  • 2.7 Update data/config.json default to include the new fields with their defaults

Verification:

  • 2.8 Run: conda run -n AniWorld python -c "from src.server.models.config import SchedulerConfig; c = SchedulerConfig(); print(c.schedule_time, c.schedule_days, c.auto_download_after_rescan)"
  • 2.9 Verify invalid time raises error: SchedulerConfig(schedule_time='25:00') should raise ValidationError
  • 2.10 Verify invalid day raises error: SchedulerConfig(schedule_days=['monday']) should raise ValidationError

Task 3: Write Unit Tests for SchedulerConfig Model

File: tests/unit/test_scheduler_config_model.py (new file)

  • 3.1 Test default values: SchedulerConfig() creates instance with schedule_time="03:00", schedule_days = all 7 days, auto_download_after_rescan=False
  • 3.2 Test valid schedule_time values: "00:00", "03:00", "12:30", "23:59"
  • 3.3 Test invalid schedule_time values: "25:00", "3pm", "", "3:00pm", "24:00", "-1:00" — all must raise ValidationError
  • 3.4 Test valid schedule_days values: ["mon"], ["mon","fri"], all 7 days, empty list []
  • 3.5 Test invalid schedule_days values: ["monday"], ["xyz"], ["Mon"] (case-sensitive), [""] — all must raise ValidationError
  • 3.6 Test auto_download_after_rescan accepts True and False
  • 3.7 Test backward compatibility: creating SchedulerConfig(enabled=True, interval_minutes=30) without new fields uses defaults for schedule_time, schedule_days, auto_download_after_rescan
  • 3.8 Test serialization roundtrip: config.model_dump() then SchedulerConfig(**dumped) produces identical config

Verification:

  • 3.9 Run: conda run -n AniWorld python -m pytest tests/unit/test_scheduler_config_model.py -v — all tests pass

Task 4: Rewrite SchedulerService to Use APScheduler with CronTrigger

File: src/server/services/scheduler_service.py

The existing service uses a custom asyncio.sleep() loop in _scheduler_loop(). Replace it with APScheduler's AsyncIOScheduler and CronTrigger.

  • 4.1 Add imports: from apscheduler.schedulers.asyncio import AsyncIOScheduler and from apscheduler.triggers.cron import CronTrigger
  • 4.2 Add a private method _build_cron_trigger(self) -> CronTrigger that converts self._config.schedule_time (HH:MM) and self._config.schedule_days (list of day abbreviations) into a CronTrigger(hour=H, minute=M, day_of_week='mon,wed,fri'). If schedule_days is empty, return None (no trigger = don't schedule)
  • 4.3 Refactor start(): instead of creating an asyncio.Task with _scheduler_loop, create an AsyncIOScheduler instance, add a job using the CronTrigger from _build_cron_trigger(), and call scheduler.start(). Store the scheduler as self._scheduler. If _build_cron_trigger() returns None, log a warning and don't add a job. Set self._is_running = True
  • 4.4 Refactor stop(): call self._scheduler.shutdown(wait=False) instead of cancelling an asyncio task. Set self._is_running = False
  • 4.5 Add method reload_config(self, config: SchedulerConfig) -> None: update self._config, then if scheduler is running, reschedule the job using self._scheduler.reschedule_job() with the new cron trigger. If trigger is None, remove the job. If scheduler is not running and config is enabled, call start()
  • 4.6 Remove or deprecate _scheduler_loop() method entirely
  • 4.7 Update get_status() to return next_run_time obtained from self._scheduler.get_job('scheduled_rescan').next_run_time (handle case where job doesn't exist). Also include schedule_time, schedule_days, and auto_download_after_rescan in status dict
  • 4.8 The job function should be _perform_rescan() (existing method) — keep it as the scheduled task. It is already async
  • 4.9 Add structured logging: log when scheduler starts, stops, reschedules, and when cron trigger is built (include the cron expression in the log)
  • 4.10 Ensure the file stays under 500 lines. If it exceeds, extract helper functions

Verification:

  • 4.11 Start the app: conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000. Check logs for scheduler startup messages
  • 4.12 Verify no import errors or startup crashes

Task 5: Implement Auto-Download After Rescan Logic

File: src/server/services/scheduler_service.py

Extend _perform_rescan() to auto-queue and start downloads after scanning.

  • 5.1 At the end of _perform_rescan(), after the scan completes successfully, check self._config.auto_download_after_rescan
  • 5.2 If enabled: call self._anime_service.get_all_anime() to get all series. For each series that has missing episodes (non-empty missing_episodes list), call self._download_service.add_to_queue() for each missing episode. Refer to the existing add_to_queue method signature in src/server/services/download_service.py to pass the correct parameters
  • 5.3 After all episodes are queued, call self._download_service.start_download() (or equivalent method from the existing download service) to begin processing the queue
  • 5.4 Broadcast a WebSocket event auto_download_started with {"queued_count": N} where N is the number of episodes queued. Use the existing _broadcast() helper method
  • 5.5 Wrap the auto-download logic in try/except. On failure, broadcast auto_download_error with {"error": str(e)} and log the error. Do NOT let auto-download failures crash the scheduler
  • 5.6 If auto_download_after_rescan is False, skip the auto-download logic entirely (just log that auto-download is disabled)
  • 5.7 Add structured logging for: auto-download started, number of episodes queued, auto-download completed, auto-download failed

Verification:

  • 5.8 Read src/server/services/download_service.py to confirm the exact method names and signatures used for queueing and starting downloads. Use those exact method names

Task 6: Update Scheduler API Endpoints

File: src/server/api/scheduler.py

  • 6.1 Update GET /api/scheduler/config: Return response that includes the new config fields (schedule_time, schedule_days, auto_download_after_rescan) plus runtime status (is_running, next_run, last_run). The response shape should be {"success": true, "config": {...all config fields...}, "status": {...runtime fields...}}
  • 6.2 Update POST /api/scheduler/config: Accept the new fields in the request body. Validate using the updated SchedulerConfig model. After saving config via config_service, call scheduler_service.reload_config(new_config) to apply changes immediately without restart. Return the same enriched response format as GET
  • 6.3 Ensure backward compatibility: if a POST request only sends {"enabled": true} without new fields, use existing/default values for the omitted fields
  • 6.4 Add proper error responses: 422 for validation errors (invalid time/days), 500 for scheduler failures
  • 6.5 The existing POST /api/scheduler/trigger-rescan endpoint should remain unchanged — it triggers an immediate rescan (which will also auto-download if configured)

Verification:

  • 6.6 Start the app and test with curl:
    • curl -H "Authorization: Bearer <token>" http://127.0.0.1:8000/api/scheduler/config — should return new fields
    • curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{"enabled":true,"schedule_time":"02:00","schedule_days":["mon","wed","fri"],"auto_download_after_rescan":true}' http://127.0.0.1:8000/api/scheduler/config — should save and return updated config

Task 7: Update Frontend HTML — Scheduler Settings Section

File: src/server/web/templates/index.html

Update the scheduler section inside the config/settings modal (lines ~245-310).

  • 7.1 Keep the existing "Enable Scheduler" checkbox (scheduler-enabled)
  • 7.2 Replace or supplement the scheduler-interval (minutes) input with a time picker input (type="time", id scheduler-time). Set default value to 03:00. Add a label: "Run at" or equivalent with data-text attribute for i18n
  • 7.3 Add 7 day-of-week toggle checkboxes (MonSun) with ids scheduler-day-mon through scheduler-day-sun. All checked by default. Display them in a horizontal row. Each checkbox label should have a data-text attribute for i18n. Use short labels: "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
  • 7.4 Keep the existing "Auto-download after rescan" checkbox (it already exists in the HTML as auto-download-checkbox around line 292). Ensure it maps to auto_download_after_rescan
  • 7.5 Hide or remove the old scheduler-interval minutes input. If keeping for backward compat, move it to a collapsible "Advanced" section
  • 7.6 Add a help text below the day checkboxes: "Scheduler runs at the selected time on checked days. Uncheck all to disable scheduling." — with data-text for i18n
  • 7.7 Add a display area showing "Next scheduled run: " that gets populated by JS (id scheduler-next-run)
  • 7.8 Ensure all new elements work in both dark and light themes (use existing CSS classes)

Verification:

  • 7.9 Start the app, open the settings modal, verify all new UI elements render correctly in both dark and light mode
  • 7.10 Verify the day-of-week checkboxes are displayed horizontally and are all checked by default

Task 8: Update Frontend CSS — Day-of-Week Toggle Styling

File: src/server/web/static/css/styles.css

  • 8.1 Add styles for .scheduler-days-container: horizontal flexbox layout, gap between items, wrapping on small screens
  • 8.2 Add styles for .scheduler-day-toggle checkboxes: styled as pill/button toggles. Unchecked = outline/muted, Checked = accent/filled. Use existing theme CSS variables for colors
  • 8.3 Add styles for .scheduler-day-label: centered text, pointer cursor, appropriate padding
  • 8.4 Ensure dark mode support using existing dark mode CSS variables/selectors (check existing dark mode patterns in the file)
  • 8.5 Ensure responsive layout: on mobile, days should wrap to 2 rows if needed
  • 8.6 Style the #scheduler-next-run display: muted text, smaller font size, italic

Verification:

  • 8.7 Test in browser: verify day toggles look like toggle buttons, dark/light mode both work, responsive on narrow viewport

Task 9: Update Frontend JavaScript — Scheduler Config Load/Save

File: src/server/web/static/js/app.js

  • 9.1 Update loadSchedulerConfig() (around line 1550):
    • Read config.schedule_time and set the time picker input value
    • Read config.schedule_days array and check/uncheck the corresponding day checkboxes
    • Read config.auto_download_after_rescan and set the auto-download checkbox (existing code at line 1562 already tries this — verify it matches the new API response shape)
    • Read status.next_run and display in the #scheduler-next-run element
    • Keep config.enabled handling as-is
  • 9.2 Update saveSchedulerConfig() (around line 1583):
    • Read the time picker value and send as schedule_time
    • Collect checked day checkboxes into an array and send as schedule_days
    • Read the auto-download checkbox and send as auto_download_after_rescan
    • Send enabled from the enable checkbox
    • POST to /api/scheduler/config with the new payload
    • On success, update the #scheduler-next-run display from the response
  • 9.3 Update toggleSchedulerTimeInput() (around line 1637): also toggle visibility of the day checkboxes and next-run display when scheduler is enabled/disabled
  • 9.4 Handle WebSocket events auto_download_started and auto_download_error — the existing code at lines 263-272 in app.js already has handlers for these. Verify they display appropriate toast notifications. Update if the payload shape changed

Verification:

  • 9.5 Start the app, open settings, change time to 02:00, uncheck "Sat" and "Sun", enable auto-download, save. Reload the page and verify values persist
  • 9.6 Check browser console for any JS errors during load/save
  • 9.7 Verify the "Next scheduled run" display updates after saving

Task 10: Update Config Service for Backward Compatibility

File: src/server/services/config_service.py

  • 10.1 Ensure load_config() handles old config.json files that don't have schedule_time, schedule_days, or auto_download_after_rescan — Pydantic defaults should handle this, but verify by reading the load logic
  • 10.2 Ensure save_config() writes the new fields to disk
  • 10.3 If there's migration logic, add a migration step that adds default values for new fields when loading an old config

Verification:

  • 10.4 Delete schedule_time, schedule_days, and auto_download_after_rescan from data/config.json, restart the app, verify defaults are used and the app doesn't crash
  • 10.5 Verify the config is saved with the new fields after any settings change

Task 11: Update FastAPI Lifespan for APScheduler

File: src/server/fastapi_app.py

  • 11.1 Verify that the lifespan function (around line 475) calls scheduler_service.start() on startup — this should now initialize the APScheduler. Confirm it works
  • 11.2 Verify that scheduler_service.stop() is called on shutdown — this should now call scheduler.shutdown(). Confirm it works
  • 11.3 Ensure no duplicate scheduler instances are created (the singleton pattern in get_scheduler_service() should prevent this — verify)

Verification:

  • 11.4 Start and stop the app, verify no errors in logs about scheduler startup/shutdown
  • 11.5 Verify logs show "Scheduler started with cron trigger: ..." or similar

Task 12: Write Unit Tests for SchedulerService (APScheduler + Auto-Download)

File: tests/unit/test_scheduler_service.py

Update the existing test file (currently 664 lines). Add new test cases; modify existing ones that test the old asyncio.sleep loop.

  • 12.1 Test _build_cron_trigger():
    • Input schedule_time="03:00", schedule_days=["mon","wed","fri"] → CronTrigger with hour=3, minute=0, day_of_week='mon,wed,fri'
    • Input schedule_time="23:59", all 7 days → day_of_week='mon,tue,wed,thu,fri,sat,sun'
    • Input schedule_days=[] (empty) → returns None
  • 12.2 Test start():
    • Mock AsyncIOScheduler. Verify add_job() is called with the _perform_rescan function and the correct CronTrigger
    • Verify scheduler.start() is called
    • Verify self._is_running is set to True
  • 12.3 Test start() with empty schedule_days:
    • Verify no job is added, warning is logged, _is_running is still set to True (scheduler is "running" but has no jobs)
  • 12.4 Test stop():
    • Mock AsyncIOScheduler. Verify scheduler.shutdown(wait=False) is called
    • Verify self._is_running is set to False
  • 12.5 Test reload_config():
    • Change schedule_time from "03:00" to "05:00". Verify reschedule_job() is called with new CronTrigger
    • Change schedule_days from all days to ["mon"]. Verify reschedule_job() is called with updated days
  • 12.6 Test reload_config() with schedule_days changed to empty list:
    • Verify the existing job is removed (scheduler.remove_job())
  • 12.7 Test _perform_rescan() with auto_download_after_rescan=True:
    • Mock anime_service.get_all_anime() to return 2 series, one with 3 missing episodes and one with 0
    • Verify download_service.add_to_queue() is called 3 times (once per missing episode)
    • Verify download_service.start_download() (or equivalent) is called once
    • Verify WebSocket broadcast of auto_download_started with {"queued_count": 3}
  • 12.8 Test _perform_rescan() with auto_download_after_rescan=False:
    • Mock same services. Verify download_service.add_to_queue() is NOT called
    • Verify no auto_download_started WebSocket broadcast
  • 12.9 Test _perform_rescan() auto-download error handling:
    • Mock download_service.add_to_queue() to raise an exception
    • Verify auto_download_error WebSocket broadcast is sent with the error message
    • Verify the scheduler does NOT crash (exception is caught)
    • Verify the rescan itself is still marked as successful
  • 12.10 Test get_status() returns correct fields: is_running, next_run_time, last_run, schedule_time, schedule_days, auto_download_after_rescan
  • 12.11 Update or remove existing tests that directly test the old _scheduler_loop() method since it no longer exists
  • 12.12 Ensure total file stays under 500 lines. If it exceeds, split into test_scheduler_service.py and test_scheduler_service_auto_download.py

Verification:

  • 12.13 Run: conda run -n AniWorld python -m pytest tests/unit/test_scheduler_service.py -v --tb=short — all tests pass
  • 12.14 Run: conda run -n AniWorld python -m pytest tests/unit/test_scheduler_config_model.py -v --tb=short — all tests pass
  • 12.15 Run coverage: conda run -n AniWorld python -m pytest tests/unit/test_scheduler_service.py tests/unit/test_scheduler_config_model.py --cov=src/server/services/scheduler_service --cov=src/server/models/config --cov-report=term-missing — target ≥80% on both modules

Task 13: Write/Update API Endpoint Tests

File: tests/api/test_scheduler_endpoints.py

Update the existing test file (currently 429 lines).

  • 13.1 Test GET /api/scheduler/config returns response with new fields: schedule_time, schedule_days, auto_download_after_rescan, and status fields is_running, next_run
  • 13.2 Test POST /api/scheduler/config with valid new fields saves correctly:
    • Send {"enabled": true, "schedule_time": "02:00", "schedule_days": ["mon","wed","fri"], "auto_download_after_rescan": true}
    • Verify response contains the saved values
    • Verify scheduler_service.reload_config() was called
  • 13.3 Test POST with invalid schedule_time ("25:00") returns 422 validation error
  • 13.4 Test POST with invalid schedule_days (["monday"]) returns 422 validation error
  • 13.5 Test POST with only {"enabled": true} (backward compat) — defaults are used for omitted fields, no error
  • 13.6 Test POST with schedule_days: [] — valid, scheduler has no scheduled jobs
  • 13.7 Test trigger-rescan endpoint still works correctly and now includes auto-download behavior when configured

Verification:

  • 13.8 Run: conda run -n AniWorld python -m pytest tests/api/test_scheduler_endpoints.py -v --tb=short — all tests pass

Task 14: Write/Update Integration Tests

File: tests/integration/test_scheduler_workflow.py

Update the existing test file (currently 514 lines).

  • 14.1 Test full workflow: Enable scheduler with schedule_time="02:00" and schedule_days=["mon","fri"] → verify APScheduler job is created with correct cron trigger parameters
  • 14.2 Test config change workflow: Change schedule_time from "02:00" to "04:00" via API → verify job is rescheduled (mock scheduler or check get_status() response)
  • 14.3 Test disable/re-enable workflow: Disable scheduler → verify job removed. Re-enable → verify job re-created
  • 14.4 Test auto-download integration: Configure scheduler with auto_download_after_rescan=True, mock anime_service to return series with missing episodes → trigger rescan → verify episodes are queued in download service
  • 14.5 Test auto-download disabled: Configure with auto_download_after_rescan=False → trigger rescan → verify no episodes are queued

Verification:

  • 14.6 Run: conda run -n AniWorld python -m pytest tests/integration/test_scheduler_workflow.py -v --tb=short — all tests pass

Task 15: Run Full Test Suite and Verify Coverage

  • 15.1 Run all tests: conda run -n AniWorld python -m pytest tests/ -v --tb=short — all tests pass, no regressions
  • 15.2 Run coverage for scheduler-related code: conda run -n AniWorld python -m pytest tests/ --cov=src/server/services/scheduler_service --cov=src/server/models/config --cov=src/server/api/scheduler --cov-report=term-missing — all three modules ≥80%
  • 15.3 Fix any failing tests from other test files that may have been affected by the config model changes
  • 15.4 Run: conda run -n AniWorld python -m pytest tests/ -v -x — stop at first failure, fix, repeat until all green

Task 16: Update Documentation

  • 16.1 Update docs/features.md: Replace "check interval (in minutes)" scheduler description with cron-based scheduling description. Add auto-download after rescan feature. Document the user-friendly time + day-of-week configuration
  • 16.2 Update docs/CONFIGURATION.md (if exists): Document scheduler.schedule_time, scheduler.schedule_days, scheduler.auto_download_after_rescan config fields with examples
  • 16.3 Update docs/API.md (if exists): Document updated GET/POST /api/scheduler/config request/response schemas with the new fields
  • 16.4 Update docs/instructions.md: Add this feature to the TODO list as completed

Verification:

  • 16.5 Review all updated docs for accuracy and completeness

Task 17: Final Manual Verification

  • 17.1 Start the app: conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload
  • 17.2 Log in with admin credentials (admin / Hallo123!)
  • 17.3 Open settings modal → Scheduler section. Verify: time picker shows 03:00, all 7 days are checked, auto-download is unchecked
  • 17.4 Change time to 02:00, uncheck Sat and Sun, enable auto-download, save. Verify toast notification confirms save
  • 17.5 Reload the page, reopen settings. Verify saved values persist
  • 17.6 Check terminal logs for: "Scheduler rescheduled with cron trigger: ..." message
  • 17.7 Trigger manual rescan. If auto-download is enabled and there are missing episodes, verify episodes appear in download queue
  • 17.8 Disable scheduler entirely, save. Verify logs show scheduler stopped
  • 17.9 Re-enable scheduler, save. Verify logs show scheduler started with correct cron expression
  • 17.10 Test dark mode and light mode — all scheduler UI elements render correctly
  • 17.11 Test on narrow viewport (mobile) — day checkboxes wrap properly

Task 18: Commit and Clean Up

  • 18.1 Review all changed files for adherence to architecture principles (single responsibility, <500 lines, type hints, docstrings)
  • 18.2 Run linter: conda run -n AniWorld python -m flake8 src/server/services/scheduler_service.py src/server/models/config.py src/server/api/scheduler.py — no critical issues
  • 18.3 Commit with message: feat: cron-based scheduler with auto-download after rescan
  • 18.4 Mark this feature as complete in docs/instructions.md TODO list