github Anyesh/wardrowbe wardrowbe-v1.11.0
wardrowbe: v1.11.0

3 hours ago

1.11.0 (2026-10-11)

✨ Features

  • auth: add support for forward auth (#242) (ef44c96)
  • auth: let a verified sign-in reclaim an email held by an unverified account (42f59a6)

🐛 Bug Fixes

  • address review feedbacks and follow ups (#244) (52d2d7c)
  • ai: treat an empty vision completion as a failure (#257) (8dd1b67)
  • analytics: show a 0% acceptance rate and 0 rating instead of no data (4d81f67)
  • api: treat a zero coordinate or rate as a value instead of missing (c3dce51)
  • auth: accept the string "true" for email_verified, as Apple sends it (b6896e4)
  • auth: fit IdP names and avatar URLs to their columns and fill a blank name from the token email (6183d21)
  • auth: flatten control characters in the IdP display name instead of refusing sign-in (d431c96)
  • auth: lock both rows in id order so two accounts swapping emails cannot deadlock (656823f)
  • auth: make a concurrent first sign-in idempotent (700aa4c)
  • auth: make a concurrent first sign-in idempotent (3eecd8b)
  • auth: never adopt an account whose email the identity source did not verify (2cd630a)
  • auth: only trust email_verified when the id_token carries the same email (f98226f)
  • auth: refuse email adoption for unverified sign-ins inside the user service (2723716)
  • auth: retry lost sign-in races against committed rows instead of returning 500 (cc8f966)
  • auth: retry when a twin sync already moved this account to the new email (7737075)
  • auth: return 409 instead of 500 when a concurrent sync claims the same email (e3f9276)
  • auth: Support Identity per provider (#255) (5f37c3b)
  • auth: treat a deactivated account as anonymous on optional-auth routes (e48f8ca)
  • auth: treat a deactivated account as anonymous on optional-auth routes (07812e0)
  • auth: treat a row with the caller's external_id as its own during a first sign-in race (c36c14c)
  • auth: validate the OIDC email claim with the shared email rule (b8d9d38)
  • colors: collapse every Unicode whitespace run the same way in SQL, Python and the frontend (2bf4822)
  • colors: derive every colour list and swatch from the vocabulary (b831459)
  • colors: drop zero-width spaces, word joiners and BOMs so an invisible name reads as blank (e2f6154)
  • colors: lowercase, trim and hyphenate colour names in the alias migrations (21e2b76)
  • colors: normalise colour aliases in item filters and outfit palettes (90c23fa)
  • colors: normalise colour aliases on item and preference writes (bf99cf0)
  • colors: read spaced colour names as their hyphenated vocabulary colour (d906d29)
  • colors: remap colour names stored in learned profiles (0972b76)
  • colors: remap stored frontend-only colour names (252b3ee)
  • compose: pass the same backend env to every worker (1d7f8fb)
  • db: commit the request session before the response is sent (4be2264)
  • docs: update repository clone URL in README (626ae50)
  • email: accept any RFC 5322 atext bare login as the sender (f1d34f0)
  • email: collapse line breaks in the subject and sender name so legacy family names still send (64bf966)
  • email: encode a non-ASCII sender name apart from the address in From (bb3c873)
  • email: fail once on an unusable sender or a permanent SMTP refusal instead of retrying (1823e9e)
  • email: log an unvalidated sender once with the validator's reason (9a432f0)
  • email: send a quoted sender local part without quoting it twice (ff9daef)
  • email: send from a sender the header parser cannot read instead of failing every retry (b0a340b)
  • email: send from the IDNA form of the sender and name the address that needs SMTPUTF8 (54a6007)
  • email: send to the IDNA form of a domain and name the SMTPUTF8 gap for non-ASCII local parts (15d5a39)
  • email: stop retrying a send the mail server refuses for lack of SMTPUTF8 (23da5f9)
  • email: warn when SMTP_FROM_EMAIL looks like an address but is not one (5d49927)
  • families: check invite addresses with the sign-in email rule before saving the invite (03fd037)
  • families: escape the inviter and family names in the invite email (5911dfb)
  • families: require a verified email to accept an email-bound invite (6e06243)
  • families: tell the inviter when the invite email did not go out (dd18c38)
  • family: find the current member by user id so a detached admin keeps admin controls (213e142)
  • family: wait for the current member before listing others or deciding admin controls (9971871)
  • frontend: read today and weather shapes from one place (f2edcad)
  • history: show skipped outfits and filter every status (e5eb35d)
  • i18n: capitalise the Italian accepted status label (5855882)
  • items: don't mark an item manually tagged when a PATCH sets only user-owned tag keys (3daf66f)
  • items: refresh outfit caches when an item's images change (75f304e)
  • learning: drop non-numeric learned colour scores at runtime and in the remap migration (603b026)
  • learning: keep the readable fields of a learned pattern instead of dropping the entry (7944222)
  • learning: read learned scores and patterns through accessors that skip malformed entries (cac6fda)
  • learning: report a 0% acceptance rate as the suggestion confidence instead of none (d774df5)
  • learning: skip learned scores too large to read as a float (b6f309a)
  • lock tag merges and protect unmirrored keys (cd347ee)
  • mattermost: escape list markers after a bare carriage return (63cd3dd)
  • mattermost: send a linked attachment title unescaped since Mattermost shows it as plain text (1ffcb5b)
  • migrations: apply the colour rule in Python so unknown names lowercase as runtime writes do (5b6cd7c)
  • notifications: bind the sent status through the outfit status enum type (43ccd5e)
  • notifications: build links and SMTP config from Settings (970788e)
  • notifications: check scheduled users for channels through the dispatcher (1130f65)
  • notifications: clear the earlier error when a retried notification is sent (bd18027)
  • notifications: don't pre-fill an undeliverable .invalid address into an email channel (c06cf1c)
  • notifications: escape line-start markdown and the attachment title sent to Mattermost (abfdedc)
  • notifications: escape markdown and mentions in user text sent to Mattermost (758e429)
  • notifications: flag a stored channel config that is not an object and keep it out of test errors (6a5cba6)
  • notifications: flag stored channel settings this version rejects instead of failing silently (87585fb)
  • notifications: keep a verdict the user gives while the outfit notification is sending (88321d1)
  • notifications: keep the outfit greeting, weather line and short push body (bbade4b)
  • notifications: keep the user's verdict when a retried outfit notification lands (2ba0159)
  • notifications: label retried outfits Today or Tomorrow from the outfit date (1a15a11)
  • notifications: mark the outfit sent when a retried notification goes out (88c2c1e)
  • notifications: name a past outfit's weekday instead of calling it today (387a486)
  • notifications: record each failed laundry channel with its own error and retry on the next run (5ab5f80)
  • notifications: record every outfit channel attempt through the laundry recorder (2612955)
  • notifications: record the laundry channels that failed before a fallback succeeded (44f9a00)
  • notifications: restore the structured outfit email and Mattermost formatting (0a78107)
  • notifications: retry the first channel that failed transiently, not a rejected config (f9c4ec5)
  • notifications: send laundry reminders through the same channel registry as the dispatcher (0bf2b4b)
  • notifications: show channels registered by the mobile app (1c0677d)
  • notifications: skip stored channels this version has no provider for (1de3965)
  • notifications: stop retrying a notification whose retry fails for good (04ef598)
  • notifications: title outfits by occasion and show whole degrees with the degree sign on every channel (0e23e90)
  • notifications: treat a stored channel this version cannot send to as not found (4b9b09e)
  • notifications: use the singular for a one-item laundry reminder (f2cce86)
  • occasions: give every occasion a formality range (5d8f0d9)
  • occasions: validate suggestions and schedules against one occasion list (f4b3749)
  • occasions: validate the default and wear-log occasions and read unlisted stored defaults as casual (e5f581c)
  • outfits: default wear dates to the user's local day (41ac74f)
  • outfits: one Outfit type shaped like the API response (b8f7d4a)
  • outfits: record mark-worn on the user's local day (cf9c2ab)
  • preserve user-set tags through updates and AI re-analysis (eeaba6b)
  • preserve user-set tags through updates and AI re-analysis (453d7ab)
  • rembg model cache (#258) (2202f56)
  • services: rule drifts and bug fixes (1f6f6f4)
  • settings: cap the location name at its column width and show one translated save error (55ed807)
  • studio: record an undated mark-worn outfit on the user's today (dede24e)
  • tagging: replace logprobs_confidence on re-analysis instead of keeping the first run's score (a1c2bca)
  • timezone: fall back to UTC in the browser as the backend does (e84bb99)
  • timezone: resolve every user timezone through one helper and validate new ones (fab5c59)
  • uploads: enforce MAX_UPLOAD_SIZE_MB per file (14f8aaf)
  • uploads: name failed files and only offer retry for retryable ones (f093659)
  • users: accept the same email addresses everywhere sign-in does (19dd7c3)
  • users: bound profile updates to the column limits instead of failing the flush (3321158)
  • users: count a name of combining marks alone as blank (66ce72c)
  • users: count interlinear annotation and Egyptian format controls as blank (d338d23)
  • users: keep rejecting .invalid and .onion email domains (1b0e469)
  • users: match blank names on Unicode's default ignorable ranges instead of every format character (0f5bcd7)
  • users: refuse a body measurement too large to read as a float (4e79c23)
  • users: refuse line breaks in the location name (1882226)
  • users: refuse names that are only whitespace or zero-width characters (62f1c01)
  • users: treat names made only of invisible characters as blank, also after the IdP cut (b18bed8)
  • validation: refuse line breaks and control characters in family and display names (7ab28e3)
  • wardrobe: count worn-ago days across daylight-saving changes (374c4cb)
  • wardrobe: paint item colour dots through the shared colour lookup (7763da4)
  • workers: charge a retry attempt only for a failed send, not for a lock error (f99c8c0)
  • workers: keep a failed rollback and a lock release error out of the wrong log line (49aa50d)
  • workers: roll back a failed notification retry so the rest of the batch still runs (8fad78a)

♻️ Refactoring

  • email: flatten header values with the shared control-character rule (84fb5e0)
  • items: share the tag-mirrored column list between update and the tagging worker (e481fdd)
  • notifications: build the laundry reminder message in one function (3ec0c4e)
  • single source rules and remove duplicates (#243) (a19750a)
  • users: drop the unused UserService.create/update and their schemas (3f5ad4b)

📝 Documentation

  • auth: note how commit order settles a reclaim racing the holder and when retries run out (a76b83e)
  • notifications: state the retryable rule instead of listing its current cases (3287868)
  • oidc: replace the dead OIDC_SKIP_SSL_VERIFY with OIDC_CA_BUNDLE (51f0f59)

🔧 Maintenance

  • remove empty and never-imported files (ee26163)
  • remove empty and never-imported files (e0884a3)

🧪 Tests

  • auth: cover case and padding variants of a detached address at /auth/sync (4ac6026)
  • auth: merge the two-session race helpers into one (24a7126)
  • auth: parametrize the adopt/refuse race and read the race outcome after every commit (a7a67b3)
  • auth: parametrize the OIDC sync tests behind one fixture and cover persisted email_verified (2d8671d)
  • auth: read the session and family of a detached account (2bd803b)
  • auth: split the adoption and changed-email tests into adopt, reclaim and refuse cases (66d85b2)
  • auth: table the OIDC email claim cases and cover a punycode claim against a unicode body (b85df9f)
  • auth: wait for the second sync to block on the first before committing (b32e458)
  • calendar: seed the profile so the today test cannot race a timeout (7b3dce3)
  • colors: check each colour alias against the stored colours instead of a tautology (5a3fc03)
  • db: assert auth and endpoint share the request session on a route that uses both (9b5151f)
  • families: assert the invite 403 details and that the email match is checked first (3230c83)
  • history: assert literal status labels instead of reading the catalog (d45e4cd)
  • invite: cover the unverified-email and wrong-email join errors (8bc1874)
  • logging: check each entrypoint's real app logger instead of a recorded call (444cd1c)
  • make the observer stubs constructible under vitest 4 (a217e0d)
  • notifications: check invite and notification email escaping in one table (16072a1)
  • notifications: check trailing-slash links once across outfit, laundry and invite emails (be4e0bd)
  • notifications: compare outfit renders with literal expected output (7d1bd67)
  • notifications: cover the first send in the verdict race test (bf8777d)
  • notifications: parametrize the single-channel laundry reminder tests (d05962b)
  • notifications: split the retry status cases out of the day label test (f8bed8a)
  • occasions: drop the schedule occasion tests the shared vocabulary tests already cover (6ad004f)
  • preferences: drop the unlisted default occasion test the shared occasion table covers (8b721f5)
  • share one session_maker fixture and restore dependency overrides after real_get_db (092a7d4)
  • timezone: cover UTC+13, Kathmandu and daylight-saving days in the user clock (3a33ceb)
  • timezone: parametrize the bad stored timezone reads and saves (c5e455e)
  • uploads: table the failure codes with their retryable flag (12ce25d)
  • workers: run the retry test through the real Redis lock and cover a missing user (cac432c)

Don't miss a new wardrowbe release

NewReleases is sending notifications on new releases.