Default wizard steps were duplicated once per server instance of the same
type (e.g., 2 Plex servers → 6 steps instead of 3). Deduplicate the
server type list before loading steps so each type's steps appear only once.
Also add the missing wizard_bundle_id parameter to the invitation creation
API, matching the existing web UI capability.
Closes#1219
Commit 73c29458f renamed library_id/limit to _library_id/_limit to
suppress ARG002 warnings, but the widget caller passes them as keyword
arguments (limit=6), causing a silent TypeError that returns empty data.
Closes#1207
Previous failed batch_alter_table attempts leave behind
_alembic_tmp_admin_account in SQLite (no transactional DDL).
Subsequent retries fail trying to CREATE the same temp table.
Now drops any _alembic_tmp_* tables before batch operations, and
splits the admin_account alter into two paths depending on whether
prior columns already exist.
The previous fix addressed the root cause (FK enforcement blocking
batch_alter_table), but users who already hit the bug have databases
with orphaned LDAP tables from the failed partial run. Since SQLite
has no transactional DDL, those tables persist despite the rollback.
Now the migration checks for existing tables/columns before creating
them, so it recovers cleanly regardless of database state.
SQLite batch_alter_table recreates tables via DROP + CREATE. With
PRAGMA foreign_keys=ON (set by our connection handler), the DROP TABLE
admin_account fails because webauthn_credential and api_key reference
it. This caused a crash loop: tables created before the crash persisted
(no transactional DDL in SQLite), then re-running created duplicates.
Add overflow-hidden to admin layout's h-screen container to prevent
body-level scrolling competing with #content's overflow-y-auto.
Mark LDAP settings as experimental with a badge.
The `tojson` filter already outputs a complete JSON object with braces,
but the template wrapped it in extra JavaScript braces, producing invalid
syntax. This caused a SyntaxError that prevented navigator.credentials.create()
from ever being called — no passkey prompt appeared.
- Fix 55 test failures caused by missing request contexts and incorrect
session_transaction() usage across 8 test files
- Fix ruff import sorting errors and unused imports
- Fix 122 type errors: rename method override parameters to match base
classes, add None guards for fetchone()/datetime, widen dict type
annotations, add type: ignore for SQLAlchemy stub limitations
- Add [tool.ty.rules] config to suppress unsupported-base warnings
- Fix _ variable shadowing gettext in wizard routes
- Add noqa: ARG002 for unused method arguments required by base class
- Fix SUBTREE→BASE scope in _fetch_user_attributes (security)
- Remove is_ldap_user threading from all 8 media client interfaces;
set flag after join via workflow-level User.query.update instead
- Use find_user_dn (search-based) instead of build_user_dn (concatenation)
in delete_user and reset_user_password for robustness
- Wrap all user-facing strings in _() for i18n in ldap_auth.py
- Fix username fallback from hardcoded "wizarr" to unique per-user
- Return failure from _update_existing_user when password update fails
- Replace dynamic Tailwind classes with explicit conditionals in alert
- Add stale group cleanup and upsert in sync_ldap_groups
- Remove fake @ldap.local email fallback in user_sync
Jellyfin and Emby collectors were recording RunTimeTicks (total file
runtime) as the session duration, causing severe overestimation when
users stopped partway through long titles.
- JellyfinCollector._emit_session_event: on session_end, use position_ms
(last known playback position) as duration_ms; fall back to
raw_duration_ms only when position_ms is unavailable or zero.
- EmbyCollector._emit_session_event: identical fix (shared API shape).
- JellyfinHistoricalImporter._process_item: for partially-watched items
(UserData.Played == false) use PlaybackPositionTicks; keep RunTimeTicks
only for fully-played items where position has already been reset.
- historical_duration_source metadata field now accurately reflects
which source was used (runtime_ticks, playback_position_ticks, or
runtime_ticks_fallback).
Plex was already correct via elapsed-time tracking in SessionManager.
- Change EmbyClient.libraries() to return {Id: Name} instead of {Guid: Name}
so Library.external_id stores the value Emby's EnabledFolders policy field
expects, matching what JellyfinClient._set_specific_folders() resolves.
- Change EmbyClient.scan_libraries() to return {Name: Id} instead of
{Name: Guid} for the same reason.
- Override _set_specific_folders() in EmbyClient with a mapping that includes
{Name: Id}, {Id: Id}, and {Guid: Id} entries, providing backwards
compatibility for existing Library records that still hold a Guid.
- In both EmbyClient._set_specific_folders() and the inherited
JellyfinClient._set_specific_folders(), when the input names list is
non-empty but all lookups fail (folder_ids ends up empty), log a structured
WARNING via structlog and set EnableAllFolders: False with an empty
EnabledFolders list — restricting access to nothing — rather than silently
falling back to EnableAllFolders: True which would grant full access.
- Replace inline debug logging.info() calls with structlog structured events.
When a user who already has a Plex share uses a new invite link, the
"already sharing" error from the Plex API is now handled gracefully by
calling update_user_libraries() and update_user_permissions() instead of
propagating the error to the end-user. The route layer also switches to
a generic, translatable user-facing error message backed by structlog for
admin-visible diagnostics.
Add created_at column to the User model, an Alembic migration to
backfill existing rows via server_default, and update the API
serialization and OpenAPI spec from the stale `created` field to
`created_at` so the joined/created date is no longer returned as null.