Skip to content

feat(airing-schedule): let admins override an anime's start season - #1498

Open
revam wants to merge 13 commits into
masterfrom
feat/start-season-overrides
Open

revam wants to merge 13 commits into
masterfrom
feat/start-season-overrides

Conversation

@revam

@revam revam commented Oct 6, 2026

Copy link
Copy Markdown
Member

Blocked by #1497 (the Airing Schedule utility): this branch is built on it and shows its commits until it's merged; review only the last commit.

Why

The season view places each anime in the season its first regular episode aired in. That season comes from AniDB's dates, and sometimes it is wrong for a chart: a late-night premiere that falls a day before a season boundary, a show whose first episode went out at an event months early, or an anime with no dates yet. The core can now store a start season set by hand per AniDB anime. This PR lets admins set it from the season view and manage the overrides in the settings.

Where this sits

  • Branch: feat/start-season-overrides, against master. It only adds to the season view and the airing schedule settings page that feat: add the Airing Schedule utility, read the dashboard's upcoming anime from it, and add its settings #1497 introduces, which is why it carries that PR's twelve commits for now.
  • Commit to review: the last one, feat(airing-schedule): let admins override an anime's start season.
  • Server: needs the server's start season override routes from the current core master:
    • GET Series/AniDB/{anidbID}/StartSeason: { Year, Season, IsOverridden, Computed: { Year, Season } | null }
    • PUT on the same path (admin) with { Year, Season } (year 1900 to 9999), and DELETE (admin), which clears the override
    • GET Series/AniDB/StartSeason/Overrides: [{ AnidbAnimeID, Title, Year, Season, CreatedAt, UpdatedAt, UserID }], where Title is the anime's preferred title, or null when the server has no data for it or the user may not see it
    • GET Series/AniDB/StartSeason/Overrides.csv (header AnidbAnimeID,Year,Season), and POST to it (admin) with a multipart file, answering { Added, Updated, Unchanged, Rejected: [{ Line, Text, Reason }] }
    • IsStartSeasonOverridden on each anime of GET AiringSchedule/Season/{year}/{season}/Sections

What

Season cards:

  • For admins, a "Move to season…" icon button in the top-right corner of each card. It opens a small modal with a season picker and a year field. They start on the anime's current start season (from GET …/StartSeason), or on the viewed season when the anime has none.
  • When the anime has an override, the modal says so and names the season its dates give ("Moved by hand. Its dates put it in Fall 2026.") and offers Reset to Computed (DELETE).
  • Save (PUT) is enabled only for a different season with a valid year. Both actions toast, close the modal, and refetch the season sections, the season lists (the slider's and the season browser's counts) and the override list, so a moved anime leaves the view right away.
  • One modal for the whole view (in SeasonView), not one per card.
  • Cards whose start season was set by hand show a small calendar-lock marker with the tooltip "Moved to this season by hand", for every user.

Settings > Airing Schedule, a new admin-only Start Season Overrides section above Provider Priority:

  • The number of overrides next to the heading.
  • Export CSV downloads start_season_overrides.csv from the server's export.
  • Import CSV is a file picker. The import's report shows under the buttons ("Imported: 1 updated, 1 unchanged, 2 rejected"), with each rejected line's number, its text and the server's reason.
  • A compact list of the overrides: the AniDB ID (linked to AniDB), the title when the server has the anime, the season, and a remove button (DELETE).
  • Like the channel merges, all of it saves at once instead of waiting for the page's Save, and the section's description says so.

Titles come with the override list itself, so the list makes no request per row. An anime the server has no data for shows "Unknown anime".

Look first at: src/components/Dialogs/StartSeasonModal.tsx, src/components/Settings/AiringScheduleSettings/StartSeasonOverrides.tsx, src/components/Utilities/AiringSchedule/SeasonCard.tsx, and the new queries and mutations at the end of src/core/react-query/airing-schedule/queries.ts and mutations.ts. Types are in src/core/types/api/airing-season.ts, pure helpers in src/core/utilities/startSeason.ts.

Feedback wanted

  • The card shows two icons when an admin looks at an overridden anime (the marker and the move button). Should the marker be folded into the button for admins, for example by colouring the button?
  • Should the modal warn when the picked season is the computed one, and suggest Reset instead of saving an override equal to it?

Testing

  • On the tip: pnpm lint, pnpm tscheck, pnpm test (262 passed; new: tests/core/utilities/startSeason.test.ts for the year range, the picker's starting season and the import summary line) and pnpm build all pass. No dependency changes.
  • Checked live against a local server on core master, as an admin, before the branch moved onto master:
    • Move: in Fall 2026, "Move to season…" on Tensei Shitara Ken Deshita II (AniDB 17789) opened on Fall 2026. Picking Winter 2027 and saving stored the override (GET …/Overrides), and Fall 2026 refetched from 34 to 33 anime. In Winter 2027 the card showed the marker, and its modal showed "Moved by hand. Its dates put it in Fall 2026." with Reset to Computed.
    • Reset: Reset to Computed in the modal cleared the override and the card left Winter 2027.
    • Import: importing a CSV with a new line, an unchanged line, a bad ID and a year of 1850 reported the counts and both rejected lines with the server's reasons. A second file with a changed season showed "1 updated".
    • Export: downloaded start_season_overrides.csv with the header and the two rows.
    • Remove: the remove buttons emptied the list, and the count went to 0.
  • Not yet live: the titles from the override list. The live checks above ran while the list still fetched a title per row; the switch to Title ("Unknown anime" for null) was checked by reading the code.
  • Not tried: a user who is not an admin. The button and the section are gated on useCurrentUserQuery().data?.IsAdmin like the channel merges; the marker shows for everyone.

revam added 13 commits October 6, 2026 16:19
- Added a `selection` option to `LanguagesModal` that hands the picked languages to the caller instead of saving a settings preference, with `multiple: false` for a single pick and `noneLabel` for an explicit "no language" choice
- Added an `exclude` option to leave pseudo-languages out of the list
- Kept every existing call site unchanged
…ion to `BackgroundImagePlaceholderDiv`

- Added a `compact` option to `MultiStateButton` for narrower, lower buttons sized to their labels, for a toolbar
- Added a `position` option to `BackgroundImagePlaceholderDiv` that sets where the image sits in its frame
- Added the Airing Schedule utility, with month, week and agenda views of `GET AiringSchedule/Calendar`, under the utilities menu
- Put the refresh, scope and channel toggles in a toolbar, the view switcher and the period navigation beside it, and the period in the title, sliding in the direction of travel
- Moved the channel filter, airing kind and provider behind a settings gear, kept with the last view for the session
- Kept the month and week grids while they load, with skeletons in each day, and drew only the agenda rows in view
- Refetched the airings on the `airing` feed's sweeps, marked what is on air now and which episodes have local files, and let debug builds shift the schedule's clock
- Added a season view, now the default, listing a season's anime in the server's sections with their next new episode and a countdown, as cards in one to three columns drawn only while in view
- Picked the season from a slider kept in view while scrolling, and browsed every season by year from it, in place of a separate archive view
- Returned to the slider, or to where you were, after picking a season or closing the browser, and kept the slider pinned while the browser opens
- Showed skeleton cards while a season or the browser loads, and refetched the season's anime every few minutes and on the `airing` feed's sweeps
- Added the season view's oldest year to the airing schedule's settings modal, saved to the user's WebUI settings once typing pauses
- Added an `Airing Schedule` settings page with general options, the "Refresh Anime Airing Soon" window, airing preferences, channel visibility, "Studio & Genre Sources", the provider priority and a section per provider
- Kept the page's changes in a draft sent by the settings page's Save with the core settings, the service's own options as a JSON patch on its configuration
- Set each provider's kinds, sweep interval and plugin configuration, and showed its icon and, when it differs, its plugin's name
- Added channel search, aliases, merges and a duplicate check, the preferred channels and tracks, and the season detail sources with `OrderList` and `TextSourcesModal`
- Switched the upcoming anime widget from `Dashboard/AniDBCalendar` to `GET /api/v3/AiringSchedule/Calendar`, without reruns, and removed the unused `useDashboardCalendarQuery`
- Laid the widget out as a timeline from 12 hours ago with a "You Are Here" mark, one card per episode and day with its channel and provider icon
- Added a link from the widget's header to the airing schedule
- Added dashboard options for the widget's days ahead and air times, kept in the WebUI settings
- Added `SequenceNumber` and `IsResolved` to the airing, and `includeUnresolved` to the airing reads
- Labelled an unresolved airing `Ep N` from its `Number`, with a muted "Not on AniDB yet" hint in the entries, cards and the dashboard widget
- Linked an unresolved airing to its series rather than an episode, and showed no local files for it
- Added a "Show Recently Aired" switch and its hours, 1 to 24, to the airing schedule settings modal, saved in `WebUI_Settings.airingSchedule.recentlyAired`
- Read the current and previous seasons' sections with `at` that many hours back, rounded down to 15 minutes, so a card keeps an episode that aired since
- Kept a season's cards while the next 15 minute step is read, which moves them on
- Showed how long ago a card's episode aired, by the clock, in place of "Just aired"
- Split the settings modal into General and Sections tabs
- Added a draggable section list with an inline editor for the title, anime types, continuing and half length, saved as you edit to `airingSchedule.sections`
- The season view always reads its sections by `POST`, with the saved layout or `null` for the server default, and hides the empty ones
- Dropped the section `ID`; sections are keyed by their place in the layout
- Added an "Episode Types" multi-select to the General tab, saved to `airingSchedule.episodeTypes`, `null` for every type
- Sent the types as `episodeType` on the season sections read and as `type` on the calendar read
- Read `Season` and `Season/ByYear` by POST with the stored layout, which joined their query keys
- Showed the left-out hint only for a custom layout without a rest group
- Added a "Move to season…" button to the season cards for admins, opening a modal with season and year pickers, the season the anime's dates give when it was moved by hand, and "Reset to Computed", through `PUT`/`DELETE /api/v3/Series/AniDB/{anidbID}/StartSeason`
- Marked the cards whose start season was set by hand (`IsStartSeasonOverridden`)
- Added an admin-only "Start Season Overrides" section to the airing schedule settings: the count, CSV export and import (with the import's report and rejected lines), and the overrides with their titles and a remove button
- Refetched the season sections and the season lists after every change

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant