29 KiB
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
- Incremental Development: Implement features incrementally, testing each component thoroughly before moving to the next
- Code Review: Review all generated code for adherence to project standards
- Documentation: Document all public APIs and complex logic
- Testing: Maintain test coverage above 80% for all new code
- Performance: Profile and optimize critical paths, especially download and streaming operations
- Security: Regular security audits and dependency updates
- Monitoring: Implement comprehensive monitoring and alerting
- 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.4torequirements.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_validatorforschedule_timethat validates HH:MM format (00:00–23:59). RaiseValueErrorwith a clear message on invalid input - 2.5 Add a
field_validatorforschedule_daysthat validates each entry is one of["mon","tue","wed","thu","fri","sat","sun"]. RaiseValueErroron invalid entries. Empty list is allowed (means scheduler won't run) - 2.6 Keep
interval_minutesfield for backward compatibility but it will no longer be used whenschedule_timeis set. Add a docstring noting this deprecation - 2.7 Update
data/config.jsondefault 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 raiseValidationError - 2.10 Verify invalid day raises error:
SchedulerConfig(schedule_days=['monday'])should raiseValidationError
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 withschedule_time="03:00",schedule_days= all 7 days,auto_download_after_rescan=False - 3.2 Test valid
schedule_timevalues:"00:00","03:00","12:30","23:59" - 3.3 Test invalid
schedule_timevalues:"25:00","3pm","","3:00pm","24:00","-1:00"— all must raiseValidationError - 3.4 Test valid
schedule_daysvalues:["mon"],["mon","fri"], all 7 days, empty list[] - 3.5 Test invalid
schedule_daysvalues:["monday"],["xyz"],["Mon"](case-sensitive),[""]— all must raiseValidationError - 3.6 Test
auto_download_after_rescanacceptsTrueandFalse - 3.7 Test backward compatibility: creating
SchedulerConfig(enabled=True, interval_minutes=30)without new fields uses defaults forschedule_time,schedule_days,auto_download_after_rescan - 3.8 Test serialization roundtrip:
config.model_dump()thenSchedulerConfig(**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 AsyncIOSchedulerandfrom apscheduler.triggers.cron import CronTrigger - 4.2 Add a private method
_build_cron_trigger(self) -> CronTriggerthat convertsself._config.schedule_time(HH:MM) andself._config.schedule_days(list of day abbreviations) into aCronTrigger(hour=H, minute=M, day_of_week='mon,wed,fri'). Ifschedule_daysis empty, returnNone(no trigger = don't schedule) - 4.3 Refactor
start(): instead of creating anasyncio.Taskwith_scheduler_loop, create anAsyncIOSchedulerinstance, add a job using theCronTriggerfrom_build_cron_trigger(), and callscheduler.start(). Store the scheduler asself._scheduler. If_build_cron_trigger()returnsNone, log a warning and don't add a job. Setself._is_running = True - 4.4 Refactor
stop(): callself._scheduler.shutdown(wait=False)instead of cancelling an asyncio task. Setself._is_running = False - 4.5 Add method
reload_config(self, config: SchedulerConfig) -> None: updateself._config, then if scheduler is running, reschedule the job usingself._scheduler.reschedule_job()with the new cron trigger. If trigger isNone, remove the job. If scheduler is not running and config is enabled, callstart() - 4.6 Remove or deprecate
_scheduler_loop()method entirely - 4.7 Update
get_status()to returnnext_run_timeobtained fromself._scheduler.get_job('scheduled_rescan').next_run_time(handle case where job doesn't exist). Also includeschedule_time,schedule_days, andauto_download_after_rescanin 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, checkself._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-emptymissing_episodeslist), callself._download_service.add_to_queue()for each missing episode. Refer to the existingadd_to_queuemethod signature insrc/server/services/download_service.pyto 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_startedwith{"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_errorwith{"error": str(e)}and log the error. Do NOT let auto-download failures crash the scheduler - 5.6 If
auto_download_after_rescanisFalse, 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.pyto 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 updatedSchedulerConfigmodel. After saving config viaconfig_service, callscheduler_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-rescanendpoint 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 fieldscurl -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", idscheduler-time). Set default value to03:00. Add a label: "Run at" or equivalent withdata-textattribute for i18n - 7.3 Add 7 day-of-week toggle checkboxes (Mon–Sun) with ids
scheduler-day-monthroughscheduler-day-sun. All checked by default. Display them in a horizontal row. Each checkbox label should have adata-textattribute 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-checkboxaround line 292). Ensure it maps toauto_download_after_rescan - 7.5 Hide or remove the old
scheduler-intervalminutes 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-textfor 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-togglecheckboxes: 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-rundisplay: 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_timeand set the time picker input value - Read
config.schedule_daysarray and check/uncheck the corresponding day checkboxes - Read
config.auto_download_after_rescanand set the auto-download checkbox (existing code at line 1562 already tries this — verify it matches the new API response shape) - Read
status.next_runand display in the#scheduler-next-runelement - Keep
config.enabledhandling as-is
- Read
- 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
enabledfrom the enable checkbox - POST to
/api/scheduler/configwith the new payload - On success, update the
#scheduler-next-rundisplay from the response
- Read the time picker value and send as
- 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_startedandauto_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 oldconfig.jsonfiles that don't haveschedule_time,schedule_days, orauto_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, andauto_download_after_rescanfromdata/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
lifespanfunction (around line 475) callsscheduler_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 callscheduler.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 withhour=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) → returnsNone
- Input
- 12.2 Test
start():- Mock
AsyncIOScheduler. Verifyadd_job()is called with the_perform_rescanfunction and the correct CronTrigger - Verify
scheduler.start()is called - Verify
self._is_runningis set toTrue
- Mock
- 12.3 Test
start()with emptyschedule_days:- Verify no job is added, warning is logged,
_is_runningis still set toTrue(scheduler is "running" but has no jobs)
- Verify no job is added, warning is logged,
- 12.4 Test
stop():- Mock
AsyncIOScheduler. Verifyscheduler.shutdown(wait=False)is called - Verify
self._is_runningis set toFalse
- Mock
- 12.5 Test
reload_config():- Change
schedule_timefrom"03:00"to"05:00". Verifyreschedule_job()is called with new CronTrigger - Change
schedule_daysfrom all days to["mon"]. Verifyreschedule_job()is called with updated days
- Change
- 12.6 Test
reload_config()withschedule_dayschanged to empty list:- Verify the existing job is removed (
scheduler.remove_job())
- Verify the existing job is removed (
- 12.7 Test
_perform_rescan()withauto_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_startedwith{"queued_count": 3}
- Mock
- 12.8 Test
_perform_rescan()withauto_download_after_rescan=False:- Mock same services. Verify
download_service.add_to_queue()is NOT called - Verify no
auto_download_startedWebSocket broadcast
- Mock same services. Verify
- 12.9 Test
_perform_rescan()auto-download error handling:- Mock
download_service.add_to_queue()to raise an exception - Verify
auto_download_errorWebSocket 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
- Mock
- 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.pyandtest_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/configreturns response with new fields:schedule_time,schedule_days,auto_download_after_rescan, and status fieldsis_running,next_run - 13.2 Test POST
/api/scheduler/configwith 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
- Send
- 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"andschedule_days=["mon","fri"]→ verify APScheduler job is created with correct cron trigger parameters - 14.2 Test config change workflow: Change
schedule_timefrom"02:00"to"04:00"via API → verify job is rescheduled (mock scheduler or checkget_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, mockanime_serviceto 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): Documentscheduler.schedule_time,scheduler.schedule_days,scheduler.auto_download_after_rescanconfig fields with examples - 16.3 Update
docs/API.md(if exists): Document updated GET/POST/api/scheduler/configrequest/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.mdTODO list