UltimateCalendar-v1.9.1
What's new
Ultimate Calendar
Formerly Classic Calendar - Revived. Entries before v1.9.1 use the old name.
[v1.9.1] (2026-10-21)
Renamed to Ultimate Calendar
Files: ClassicCalendar.toc, ClassicCalendar_TBC.toc, ClassicCalendar_Camelot.toc, ClassicCalendar.lua, ClassicCalendar_Options.lua, GameTime_Wrath.lua, Patches.lua, HolidayData.lua, Forever.lua, WorldBuff.lua, WorldBuffDB.lua, Tests/env_cc.lua, README.md, API.md, docs/Curseforge_Description.{md,html}, CLAUDE.md.
Operator, 2026-10-21: "i want to rebrand the addon as Ultimate Calendar". The
CurseForge project is now "Ultimate Calendar", slug ultimatecalendar; the
project ID (1339939) is unchanged, so X-Curse-Project-ID stays.
- Display name only. Every TOC
## Titleis now|cffFF8000Ultimate Calendar|r(Forever keeps its grey "Forever" suffix). The minimap tooltip, LDBtooltip, the Settings category and itsOpenToCategoryfallbacks say "Ultimate Calendar", and chat lines that startedClassicCalendar:now startUltimate Calendar:. - Kept on purpose: the
ClassicCalendarfolder and TOC file names, every SavedVariable, global, frame name andClassicCalendar.API, the LibDBIcon key (it holds the minimap button position) and the AceComm namespace. Renaming any of those would lose saved data, break other addons'OptionalDeps, or stop old and new versions talking. - Release tags are now
UltimateCalendar-vX.Y.Z, starting with this one (operator: "change the tag to UltimateCalendar-v1.9.1"). The packager reads only the version from the tag, so nothing in the addon depends on the prefix. - VersionCheck name stays "Classic Calendar - Revived". VersionCheck-1.0
keys every peer's version by that string on the wire (
VC.hosts[name], the REQ batch), so a new name would mean older installs never hear that a newer version exists. KNOWN COST: the version window's tab still shows the old name.
[v1.9.0] (2026-10-20)
Classic Era: the Edge of Madness boss is on the calendar
Files: HolidayData.lua, Locales/localization-world.lua, Tests/edgeofmadness_spec.lua.
crimsonmane on Discord, 2026-10-20 (inbox edea3a57): "is it possible for the calendar addon to see which Madness boss is active?" My first answer said no, after one search that missed the trackers; the operator: "there are sites dedicated to the schedule we can pull in". The answer on Discord was edited.
EdgeOfMadnessScheduleFor(year)lists every two-week boss window that starts in that year: Gri'lek, Hazza'rah, Renataki, Wushoolay, 14 days each, counted from Gri'lek on Tuesday 22 September 2026. edgeofmadnesscalendar.com and zockify's Classic calendar both put Renataki on from 20 October to 3 November 2026; sources and doubts are indocs/Event_Date_Sources.md. The client has no API for it.- Days are counted from noon with
addDaysToDate, not by the holidayfrequencyrecurrence: that steps whole seconds, so after a daylight-saving change a midnight start lands on the day before. - Shown Tuesday 00:00 to Monday 23:59 in realm time (
keepTimes), under the Weekly Holidays filter, with no banner art. The change happens at midnight Pacific; the whole-day display does not show the hour. - Classic Era and Hardcore only. Season of Discovery, Burning Crusade and WoW Forever get none, since their rotation is not sourced.
- Names and descriptions are new enUS entries in
L.HolidayLocalization(EdgeOfMadnessGrilekand so on); other locales fall back to them throughHolidayText. - Specs:
Tests/edgeofmadness_spec.luapins both trackers' autumn 2026 dates, checks six years of windows run back to back Tuesday to Monday in order across daylight-saving changes, draws Renataki on a grid day and opens its description, and checks SoD, TBC and Forever show none. - Seen in game (operator, 2026-10-20, Classic Era): "edge of madness workign in classic".
- Problems panel: three
duplicate-set-fieldwarnings inTests/patches_spec.lua, where each example installs its ownC_Calendar.GetEventIndex, are disabled on those lines only. Suite 999 passed / 0 failed. Dev-only.
Release docs and lint config
Files: docs/Curseforge_Description.*, .markdownlint.json, .pkgmeta, every Markdown file in the repo.
Peer Review on the v1.8.1 release-prep self-audit (thread a626e480). This work landed after v1.8.1 was tagged, so it ships here. No Lua changed.
- The v1.7.2 Recent Updates block listed Fixed before Changed; it is now New, Changed, Fixed like the others, in both CurseForge files.
- The v1.8.0 popup bullet said the new look "arrives with the next
LibAceGUIWidgets update". That update is out (v0.4.0, MINOR 42, carries
DIALOG_STYLES.window; the library is at v0.6.0), so the line now says an up-to-date library gives the new look. .markdownlint.jsondisabled ten rules and both changelogs disabled MD024 inline. All are back on, MD013 (line length, 80) included: a disabled rule is a finding, not a setting to respect (Peer Review directive 6c26b41b). MD024 is nowsiblings_onlyin the config instead of off.README.mdanddocs/Curseforge_Description.mdwere fixed to pass: the title is the first line, list markers take one space, the New/Changed/Fixed labels and patch lines are real headings, and stray double blank lines and a trailing space are gone.docs/BUG_TRACKER.mdand the otherdocs/files got the same kind of fixes: bold pseudo-headings became real headings, italic placeholder lines became plain sentences, and bare code fences name a language.- Every Markdown file is wrapped to 80 columns. Only line breaks changed,
apart from these edits MD013 cannot be met without: section headings
longer than 80 moved their file list onto a
Files:line below, long code lines in examples were split across lines, and over-long tables were turned into lists (one bullet per row, the first column in bold). One table was kept instead: the coverage table inTests/README.mdhas shortened Notes cells, with the full notes in a paragraph below it. Indocs/BUG_TRACKER.mdthe moved heading tails were made into sentences: a capital letter, a closing period, and "Also:" where the heading had used "+". CHANGELOG_ARCHIVE.mdwas shipping in the zip. It is now in.pkgmeta's ignore list.- Test harness pinned at
21d2f0e. Its.pkgmetachecker now matches theCalendarHelperdependency to the slugcalendar-helper, so the desk'slint_allis clean on every checker instead of showing three known-false failures (WoWAPITesting thread 35f402e7). Suite 994 passed / 0 failed on the new pin. Dev-only; nothing ships. README.md(which ships) pointed players at the upstream fork. Its CI badge and "report any issues" link went to github.com/birkholz, and its Discord badge to an old invite. The CI badge is gone, and both now point at the Discord the CurseForge page uses (Peer Review thread ae59aee6).- CurseForge page: "Revinging" is now "Reviving", and the .md gains the html's "only the last five patches" line, both now saying "the addon's full changelog" rather than "on GitHub".
CHANGELOG_ARCHIVE.mdcarried the live file's "Older releases" pointer, moved along with the sections it described; removed.
[v1.8.1] (2026-10-20)
WoW Forever: dungeons and raids can be picked in Create Event again
Files: Forever.lua.
N3zr0k on Discord, 2026-10-18 (inbox 6927d964): "Dungeons and Raids can't be selected in the Events".
- Forever's client has no global
GetDungeonNameWithDifficulty. Era and TBC define it inBlizzard_UIParent/Shared/UIParent.lua; Forever keeps only privatelocalcopies in its ownBlizzard_Calendar/Mainline/Blizzard_Calendar.lua:62andBlizzard_StaticPopup_Game/GameDialogDefs.lua:5. The ported calendar calls it for every dungeon or raid name it draws -- each texture-picker row (CalendarTexturePicker_InitButton), the chosen dungeon in Create and View Event, raid lockout and reset titles in the grid, day tooltips, the event picker and the Today view -- so on Forever the picker raised "attempt to call a nil value" on its first row (the customer's error file, below).Forever.luanow defines Era's function unchanged, only when the client has none. Era and TBC never load that file. .luarc.jsondeclaresNORMAL_FONT_COLOR_CODE,FONT_COLOR_CODE_CLOSEandDUNGEON_NAME_WITH_DIFFICULTYfor the language server.- Specs:
Tests/forever_spec.luachecks the function is supplied without the client global, formats with and without a difficulty, and leaves a client's own copy alone; it also draws real Create Event picker rows (CalendarTexturePicker_InitButtonon aCalendarTexturePickerButtonTemplatebutton) for dungeons and raids, using Era's positionalDUNGEON_NAME_WITH_DIFFICULTYstring"%1$s (%2$s)". Those globals are reset by the harness from pin1d8ae40(next bullet), which replaced this spec's own save/restore (Peer Review B-N1 on 420602ab) and itsGetDifficultyInfostub. - Test harness pinned at
1d8ae40, which installsGetDifficultyInfo,GetDungeonNameWithDifficulty(non-Mainline, non-Forever) andDUNGEON_NAME_WITH_DIFFICULTYon every reset (WoWAPITesting inbox 60f1ea2b).Tests/forever_spec.luadropped its own save/restore of the two globals, itsDUNGEON_NAME_WITH_DIFFICULTYstand-in and itsGetDifficultyInfostub; it now seedswow.difficulties[0], the id every picker texture carries (Patches.lua), and still clears the harness's EraGetDungeonNameWithDifficultyso the examples testForever.lua's copy. Suite 994 passed / 0 failed on the new pin. Dev-only; nothing ships. Tests/todayview_spec.luadepended on spec-file order. Five of its examples draw a holiday with art, which calls_CalendarFrame_GetTextureCoordsfromClassicCalendar.lua, a file the spec never loaded; they passed only because an earlier file had left the global behind, and--order reversefailed all five. The spec now loadsClassicCalendar.luaafterCalendarTodayView.lua, as every TOC does. 994 passed / 0 failed forward, reversed and shuffled (seed 1792423524). Dev-only.- The customer's report was this error. Their attached
LUA_Error.txt(inbox 0102257e), read on 2026-10-20, isGetDungeonNameWithDifficultybeing nil atClassicCalendar.lua:5810, insideCalendarTexturePicker_InitButton. - Seen in game (operator, 2026-10-20, WoW Forever): Create Event with type
Dungeon and type Raid both open the picker, a raid can be chosen, and
BugSack shows no errors. Raids show without "(Normal)", which is right:
Patches.luagives every raid texturedifficultyId=0, and the name comes fromGetDifficultyInfo(0)(ClassicCalendar.lua:1024), which on Forever gave none, as seen in game.
One shipped API reference, with its limits in bytes
Files: API.md, README.md, GameEvents.lua.
Peer Review on self-audit 32e60fef, after v1.8.0 was tagged.
docs/Addon_API.mdis nowAPI.mdat the addon root..pkgmetaignoresdocs, so the reference never reached the zip; v1.8.0 worked around that with a second, condensed copy inREADME.md, and nothing asserted the two agreed. The README section is gone, replaced by one line pointing addon authors atAPI.mdin the addon folder, which also keeps the README non-technical as its own rule asks. The v1.8.0 entry below still names the old path; that is what shipped.- The text limits are bytes, not letters.
AE.Cleancuts at n bytes and backs off to a UTF-8 boundary (AddonEvents.lua), so the 64 and 1000 for added events and the 31 and 255 for personal and guild events are byte counts: a German, Russian or Korean title keeps fewer letters than the event window would take by hand. Safe, never longer.API.mdand the header comment inGameEvents.luanow say bytes. No code change. - Test harness pinned at
bd5ba2a, andrunTimersre-checks each timer before calling it (Tests/env_cc.lua). Since harness79e230ca cancelled or spent C_Timer entry hasfn = nil, so a callback that cancelled a later timer in the same batch would have raised "attempt to call a nil value" (WoWAPITesting inbox bb4a6020).Tests/syncstartup_spec.luacarried its own copy ofrunTimersand now uses the shared one, so the next timer change is made once (Peer Review on self-audit 3f5eaedc). Suite 990 passed / 0 failed on the new pin. Dev-only; nothing ships. - Correction to v1.8.0: the windowed popups need LibAceGUIWidgets
v0.4.0 (MINOR 42), not "v0.3.2" as the v1.8.0 entry says. The library's
earlier replies named v0.3.2 in error (Peer Review a87bb068). The code checks
MINOR 42 /
DIALOG_STYLES, not the version string, so only the text was wrong.
[v1.8.0] (2026-10-13)
Other addons can add events to the calendar and read every event on a day
Files: AddonEvents.lua, Patches.lua.
Asked for on Discord by the author of the "Can't Play Forever" clock addon (inbox b23c8ec5 / 8627351b): alarms shown in the calendar, and "is there an event today" shown on the clock. Operator, 2026-10-13: "build an API that allows people to add events of the proper type with the proper tie in to their character, as well as read events".
ClassicCalendar.APIis version 2. New:AddEvent(source, info),RemoveEvent(source, key),ClearEvents(source),GetEvents(source),GetDayEvents(year, month, day),GetTodayEvents(), theEVENT_TYPEStable and anEventsChanged(source)callback. The version-1 raid read API (UpcomingRaids,Signups,LootLink,RaidsChanged,SignupsChanged) is unchanged.- Events have a real type and belong to the character.
eventTypeis RAID, DUNGEON, PVP, MEETING or OTHER (the client'sEnum.CalendarEventType). Events are saved in a new per-character saved variable,ClassicCalendarCharDB, so they show on that character's calendar only and are never synced to the guild. Times are calendar (realm) time, the same clock the calendar shows. Adding the samekeyagain replaces the event; each addon may keep up to 1,000. - On the calendar they use the calendar's existing
SYSTEMkind, which the ported code already formats, colours and draws, and which it does not treat as player-made, so the day menu never offers to copy or delete one through the server. Clicking one opens the information panel the holidays use, showing the event's text and "Added by" the addon's name. An event is listed on the day it starts. - Wiring in
Patches.lua: the uncached day count and getter appendClassicCalendar_AddonEventsOn(year, month, day), and the month cache's signature carriesClassicCalendar_AddonEventsVersion, so an added event shows on the next redraw without/calrefresh.stubbedOpenEventandnewGetHolidayInfogained the added-event branch. New locale stringADDON_EVENT_ADDED_BY(enUS; other locales fall back to it). - Reading a day goes through the same
stubbedGetNumDayEvents/stubbedGetDayEventthe grid uses, so holidays, resets, guild events and added events all come back, filters applied, without moving the client's calendar month. The client's own events exist only for months the server has sent. - TOCs:
AddonEvents.luaafterRaidSignupsSync.luaandClassicCalendarCharDBinSavedVariablesPerCharacter, in all three. - Bulk adds:
AddEvents(source, list)adds a whole list with one redraw and oneEventsChangedat the end, and returnskeys, reasonsper entry. Each entry runs underpcall, so one that raises is reported as refused instead of leaving the batch open. Without it,AddEventredraws the open calendar on every call, so an addon adding hundreds of events in a loop froze the client (self-audit ba3d385a, confirmed by Peer Review). Not a timer: directive b15a1e31 keeps timers off anything that draws, and the writ check refused aC_Timer.After(0)redraw. - Saved events show at login. An index or month built before the
per-character saved variable loaded kept the empty store all session;
ADDON_LOADEDfor ClassicCalendar now drops the index and bumps the version. - Text from another addon: title and description are cut on a UTF-8
character boundary (a byte cut left broken Cyrillic or Korean) and trimmed
again after the cut, and a text that comes out empty is refused (title) or
dropped (description).
|escapes are removed whole -- colours and textures go, a link keeps its display text, a|nline break becomes a space, any stray|goes -- so another addon cannot draw textures, links or colours in the grid (Peer Review a9fbedb3 F1-F3).sourceis trimmed, so"Clock"and"Clock "are one owner (Peer Review, LOW). - Specs: new
Tests/addonevents_spec.lua(37 examples), 100% line coverage ofAddonEvents.lua.Tests/env_cc.luaresets the new globals.Tests/clientmodel_spec.luapinsAPI.EVENT_TYPESandPatches.lua'sCalendarEventTypeto the client'sEnum.CalendarEventType. - README carries the API reference.
docs/is in.pkgmeta's ignore list, sodocs/Addon_API.mdnever reaches the zip;README.md(which ships) now has a "For addon authors" section covering versions 2 and 3. Keep the two in step when the API changes. - Added events do not raise the calendar's event alarms, which read the
client's own events only. Documented in
docs/Addon_API.md. - Seen in game (operator, 2026-10-13, Classic Era):
AddEvent,GetTodayEventsandClearEventsworked from/run. Not checked: how the information panel lays out an added event's text, and TBC or WoW Forever.
Other addons can ask for personal and guild events, and edit or delete them
Files: GameEvents.lua, RaidTemplates.lua, AddonEvents.lua, ClassicCalendar.lua.
Operator, 2026-10-13: "we need the API to allow the both the guild event and the personal event type read/write, for the alarm one it probably doesn't need it, but other addons may".
ClassicCalendar.APIis version 3. New:CreateGameEvent(source, info)(kindPLAYER or GUILD_EVENT),EditGameEvent(year, month, day, index, changes, eventID)andRemoveGameEvent(year, month, day, index, eventID).- The player's press saves it.
C_Calendar.AddEventandC_Calendar.UpdateEventcarryHasRestrictionsin the Classic Era client'sCalendarDocumentation.lua, and the documentation does not say what the restriction is. So no call saves anything: create opens the calendar at that month and runs the day menu's ownCalendarDayContextMenu_CreateEvent/_CreateGuildEventon the real day cell, then fills the window; edit opens the event withC_Calendar.OpenEventand fills the edit window once, after the calendar's ownCALENDAR_OPEN_EVENThandler has shown and loaded it (a post-hook onCalendarFrame_OnEvent), and only when the opened event is the one asked for; remove selects the event and asks the calendar's delete question. Create, Update or OK is the player's. - An index is checked against its eventID (Peer Review eec90a18, F1). An
index is a place in a list the server can change between the caller's read
and its request. When the event at that index has an
eventID, edit and remove now require the caller to pass the same one, and refuse otherwise ("a different event is at that index now: read the day again"); an event without one is still found by its place. Before this, a stale index could put the player's "delete this event?" question on a different event. - The API's delete question names the event: its title and short date
above the usual wording, through the new
ClassicCalendar_ConfirmDeleteSelectedEvent(heading). The day menu's question is unchanged; it shares the wording withCalendarDayContextMenu_DeleteEventthrough one local. - A redraw no longer drops a requested edit (Peer Review eec90a18, F2).
The first version consumed the request on the edit window's first redraw,
and a roster or event update redraws an open window before the server
answers, so the API returned true for an edit that never appeared. The
request now waits for the server's open answer, for up to
GE.PENDING_SECONDS(30): a request the server never answered is dropped when it is that old, so it cannot fill the event when the player opens it by hand later. Checked when the answer arrives, not on a timer. - One edit at a time (Peer Review 36642e8e, C2). A second
EditGameEventbefore the first one's open answer replaced it, and the first event's answer then ended the second unfilled while both calls had returned true. A new edit is now refused with "another edit is waiting for the server: try again shortly" while one younger thanGE.PENDING_SECONDSwaits.docs/Addon_API.mdstates this and the 30-second drop. - CalendarHelper's raid-event path opens the right month (found in self-audit, plus Peer Review 728b6b30 N1). Its stand-in day cell kept the offset from before it moved the calendar, so the create window named the wrong month and weekday whenever the calendar had been showing another month; it is now 0, the shown month, as in the World Buff path. And its move to the target month started from the current month but stepped the year from the displayed one, so it missed by twelve months whenever the calendar showed another year.
- One month step, not three (Peer Review 36642e8e, C3). CalendarHelper's
reset-then-step block and the World Buff announcement's own step are gone;
both call
GE.ShowMonth(year, month), which measures from the month the calendar shows, so the copies cannot drift apart again. Changed: when the calendar cannot be opened or reports no month, both now print "Cannot open calendar window" and stop; before, CalendarHelper went on over a hidden calendar and the World Buff path fell back to the game date. - One month-offset sum. "How many months from the shown one" was written
four times (
GE.ShowMonth, the API's event lookup,API.GetDayEvents, Today View's click-time offset). It is nowGE.MonthOffset(year, month), nil when the calendar reports no month; the API's lookup also stops raising on that nil and refuses with "no such day on the calendar" (one example inTests/raidtemplates_spec.lua). Today View reads GameEvents unguarded in both its offset and its day-cell ID, one policy instead of two.Tests/todayview_spec.lualoadsGameEvents.lua, as every TOC does. - Today View's day menus use the month as it is at the click (Peer Review 36642e8e, C1). The right-click and open handlers read the month offset the last redraw stored, and the player can page the calendar without the Today View redrawing, so the day menu could act on another month. They now compute it from the view's date when clicked.
- Stand-in day cells name the right weekday. The calendar names the
weekday from a day cell's ID. The World Buff announcement and the three
Today View day-menu and open paths built stand-in cells whose ID was always
1, so the create window's date line named the first column's weekday
whatever the date. CalendarHelper's stand-in ignored the week's starting
day, so with the week starting on Monday it named the day after. All now
take the position from
ClassicCalendarGameEvents.CellIndex(day, monthOffset), whichGE.DayButtonalso uses. - Editor warnings: a zero-argument
GetGuildSettingstub inTests/raidtemplates_spec.luamade the language server type every real call as taking none (eight warnings acrossClassicCalendar.lua,WorldBuff.luaandClassicCalendar_Options.lua); the stub now has the real signature. Two nil-assignment warnings inCalendarHelper:IsPacificDSTare casts, since every March has a second Sunday and every November a first. - Refusals match the calendar's own rules: today or later and no later
than
C_Calendar.GetMaxCreateDate; a guild event needs a guild and passes the officers-only setting; edit and remove needContextMenuEventCanEdit/CanRemove; guild announcements are not created, as the day menu does not offer them. Title cut to 31 letters and description to 255, the window's own limits, with the same text cleaning as added events. - Reading:
GetDayEventsgives the game's own eventsindex,eventID,invitedBy,inviteStatus,modStatus,isLocked,canEditandcanRemove. - One permission check: the day menu's guild-event gate is now
ClassicCalendar_CanCreateGuildEvent(), used by the menu, the API andWorldBuffs:CreateGuildEvent(Peer Review P1 on self-audit ff59da84). The World Buff announce kept an inline copy of the officers-only rule with noIsInGuildtest, so a guildless player reached the date picker for a guild event; it now says "You must be in a guild to create a guild event." and stops. Its officer test was always the sameWorldBuffDB:IsOfficer(), so only the guild test is new. Three examples inTests/calendarhelper_spec.lua. - One form filler:
RaidTemplates.Form.Fillfills the create or edit window from any subset of title, type (with picture), time, description and lock;Form.Apply(templates) now goes through it. - Minimap button spec under the coverage run: the button's
PLAYER_LOGINframe was a local nothing referenced, which the offline harness collects (the client never does); under the coverage run that happened before login and five minimap specs failed. The frame is now kept onClassicCalendar. - Specs:
Tests/raidtemplates_spec.luagains 13 GameEvents examples on the real windows (the server's open answer delivered through the calendar's own handler; the clock pinned in October, the events' month) and 7 for the template menu, prompts, loot setter, menu button and picture path.Tests/deleteconfirm_spec.luachecks the named question;Tests/addonevents_spec.luachecksPLAYER_MADEagainst the calendar's own_CalendarFrame_IsPlayerCreatedEventthrough the source. 100% line coverage ofGameEvents.lua,AddonEvents.luaandRaidTemplates.lua.Tests/todayview_spec.luachecks the right-click month after the calendar pages;Tests/calendarhelper_spec.lualoadsGameEvents.lua, which both create paths now use. Suite 979. - Seen in game (operator, 2026-10-13, Classic Era): create, edit and
delete each worked from
/run, as didAddEvent,GetTodayEventsandClearEvents. The filled-in edit survived the window's redraws, the Create press in a window the API opened saved the event, and the create window's date line named the right weekday.
Calendar prompts open beside the calendar, not over it
Files: Patches.lua, ClassicCalendar.lua, RaidTemplates.lua.
Operator, 2026-10-13, after testing the API's delete question in game: "it should windowsnap to the next top/right open area".
- New
ClassicCalendar_PlaceBesideOpen(popup). It puts the popup's top-left corner against the top-right corner of the rightmost calendar window that is showing (the calendar, Today View, the event, holiday and raid-reset windows, the class-count strip beside them, the World Buff windows, Auto-Moderators), with the same 16-pixel drop as the docked side windows when that window is the calendar itself. Edges are compared in screen units, since those windows do not share a parent. Beside any other window the popup is level with that window's top, so beside the class-count strip it starts 30 pixels below the event window's top. When the space would run off the right or the bottom of the screen (the bottom check from peer review 0df90615, C1), or none of them is open, the popup stays where LibAceGUIWidgets centred it. - Used by all three
W:ShowDialogprompts: the event delete question and the raid template save and delete prompts. The library re-anchors its one shared dialog on every call, so moving it after the call leaves nothing behind for the next addon's prompt. - The same look as the addon's other windows. Operator, 2026-10-13: "you
need to use the same border/look/feel for the popup as you do the others."
All three prompts pass
style = "window"and atitle("Delete Event" from the client'sCALENDAR_DELETE_EVENT, "Save Raid Template", "Delete Raid Template"), so LibAceGUIWidgets draws them withBasicFrameTemplateWithInset, the title bar, close X and inset the World Buff and Auto-Moderators windows use. Its close X takes the Cancel path. This needs LibAceGUIWidgets MINOR 42 (v0.3.2, inbox 82cae97e). An older library ignores the two fields and draws the plain dialog. - Specs: six placement examples in
Tests/deleteconfirm_spec.lua: beside the calendar, past a window already open, past the class-count strip (peer review C2), stays put off the right, stays put off the bottom, nothing open moves nothing. One look example checks the window style, the title, and that the close X deletes nothing and leaves no modal.Tests/raidtemplates_spec.luaasserts the style and title on both template prompts. The "closes only its own prompt" example now covers another addon's prompt in each look. The examples hide the calendar again afterwards with its scripts removed, since a calendar left shown kept answering later spec files' calendar events. Suite 990.
[v1.7.4] (2026-10-11)
Menus and pickers moved off UIDropDownMenu
Files: ClassicCalendar.lua, CalendarTodayView.lua, ClassicCalendar_Options.lua, GuildRankConfirm.lua, WorldBuff.lua.
FastGuildInvite finding, inbox thread 0c878e13, from the operator's WoW Forever
taint.log (2026-10-05). Any addon call to UIDropDownMenu_Initialize,
ToggleDropDownMenu or CloseDropDownMenus writes the shared
UIDROPDOWNMENU_* globals from insecure code. Three of ours ran from XML
OnLoad (the hidden CalendarFilterDropDown, CalendarAddonDropDown and
CalendarTodayViewFilterDropDown) and three more at ADDON_LOADED (the
options pickers), so the addon tainted those globals at every login.
- Calendar buttons: the calendar's Filter and Add Events arrows and the
Today View's Filter open Blizzard_Menu menus through
CalendarToggleButtonMenu(first click opens, second closes, the client closing it by itself is noticed). Both Filter buttons share one generator,CalendarFilterMenu_Generate, where there were two copies. The three hidden dropdown frames are gone fromClassicCalendar.xml. - Our own pickers: the options panel's Start Day and Event Alarm pickers, the Rank Auto-Confirm control and the World Buff "Dropped" box are LibAceGUIWidgets dropdown boxes. The options pickers refresh their text when the panel shows and after a pick, instead of every frame.
- Filter defaults at load: the removed Filter dropdown's
OnLoadwas what wrote"1"into every filter setting nobody had chosen, andHolidayIsEnabledlists a holiday only when its setting is exactly"1". Without it every holiday stayed hidden until the player opened Filter once.ClassicCalendar.luanow applies those defaults at load, World Buffs excepted (it lives inCCConfig). Caught byforever_specgoing red; diagnosis confirmed by Peer Review reply afe4e100.HolidayIsEnabled(HolidayData.lua) also now hides a holiday only on an explicit"0", the rule the raid-reset filter already used: a setting the client never registered reads nil andSetCVarcannot create it, so the default alone would leave holidays hidden there. Specced inTests/holidaydata_spec.lua, red first. That rule is now one function,CalendarFilterShown(HolidayData.lua), asked by the holiday list, both raid-reset checks inPatches.luaand the Filter checkboxes: the checkboxes readGetCVarBool, so on an unregistered setting the box showed unticked while the calendar listed the holidays, and ticking it could never stick. The checkbox also registers a setting the client lacks (C_CVar.RegisterCVar, falling back to theRegisterCVarglobal) before defaulting it. Peer Review reply 65931f41 (G1);Tests/buttonmenus_spec.luamodels a client that refusesSetCVaron an unregistered name, red first. Whether an addon-registered CVar keeps its value across sessions is not verified; the "anything but 0" rule shows holidays either way. - World Buff events stayed hidden after creating one:
WorldBuffs:CreateGuildEventWithDate(WorldBuff.lua) meant to switch the World Buffs filter back on, but tested and set acalendarShowWorldBuffsCVar, while that filter lives inCCConfig.calendarShowWorldBuffs. So a player who had unticked World Buffs created an event they could not see. It now switches theCCConfigvalue on and redraws. Found by this session's self-audit;Tests/calendarhelper_spec.lua, red first. - Behaviour changes: the open sound plays only when a menu actually
opened; the alarm number picker shows an empty list instead of raising when
no alarm unit is saved; Raid Events' officer refusal no longer calls
CloseDropDownMenus, since a menu button returning nothing closes the menu. - Rank Auto-Confirm closed other addons' dropdowns: closing an event
window called
CloseDropDownMenus(), which shuts whatever dropdown any addon had open. It now closes only its own menu. - A new World Buff entry opened as "Dropped: Yes" straight after editing
an entry marked Yes: add mode reset the name and date but not Dropped.
WorldBuffs:SetDroppedValueresets it. - Known cost: the ported Create Event and Mass Invite forms still
initialise UIDropDownMenu from their
OnLoad; the calendar itself is left alone by standing directive, so login taint is reduced, not removed. - Specs: new
Tests/buttonmenus_spec.lua(filter entries and ticks, defaults at load with a holiday listed and no menu opened, toggle open/close, modal close, Add Events entries and officer refusal, the Dropped reset, and a scan that no shipped file toggles or closes the shared dropdown and only the two ported forms still initialise one).Tests/rankconfig_spec.luareads the library's item list.Tests/calendarhelper_spec.lua's create examples now clear the Create Event form functions an earlier spec may have loaded, instead of depending on file order. - Not seen in game: the boxes' positions were derived from the old dropdown padding, not by looking.
/ch create printed a developer dump into chat (CalendarHelper.lua)
CalendarHelper peer-review thread 06a03a90 (2026-10-11): the "Event Form State
Before Create" block in CreateGuildEventWithDate, a
C_Timer.After(0.1, ...) inside the time-setting callback, printed about
eleven diagnostic lines on every event import. Every other diagnostic in that
function is behind DEBUG_MODE; this one was not.
- Fix: the block returns early unless
DEBUG_MODEis on. - Specs:
Tests/calendarhelper_spec.lua"event creation chat output" drives the whole create path through its nested timers and asserts the dump is absent with debug off and present with debug on, so the first example cannot pass by never reaching the block. Proven by a red run: with the guard removed, the debug-off example fails and lists the dump. Its time stand-in is gone; the spec loads the realRaidTemplates.lua, whoseTo12gets midnight right (Peer Review reply d4073f0f).
The World Buff announce printed three debug lines too (WorldBuff.lua)
Found by reading WorldBuffs:CreateGuildEventWithDate, the function
CalendarHelper's create was copied from; a text search for the CalendarHelper
dump's wording had missed it. Every announce printed "WorldBuffs Debug: Setting
event title", "Title field contains" (or "CalendarCreateEventTitleEdit not
found!") to every player.
- Fix: all three are behind
DEBUG_MODE. The player-facing "Guild event pre-filled" and "Opening guild event creation" lines stay. - Specs:
Tests/calendarhelper_spec.lua"World Buff event creation chat output", debug off (no debug lines, the confirmation still prints) and debug on (both lines print). - And at every login:
WorldBuffs:Initialize, run fromADDON_LOADED, printed "ClassicCalendar: WorldBuff module loaded" and a "WorldBuff Debug: ... calendarShowWorldBuffs ..." line to every player. All three are behindDEBUG_MODEnow. Peer Review D1 on self-audit e7184602.Tests/calendarhelper_spec.lua"World Buff module login chat output" fires a realADDON_LOADED: nothing printed with debug off (red first, 2 lines), both lines with debug on. A read of every otherprintinWorldBuff.luafound the rest either gated or answering a player action.
The World Buffs checkbox read an unset value as off (ClassicCalendar.lua)
The calendar's World Buff display filter treats an unset
CCConfig.calendarShowWorldBuffs as shown; CalendarFilter_IsSelected read
the same unset value as unticked whenever CCConfig already existed. It now
uses ~= false, the display filter's rule, and the toggle still flips
correctly because it is computed from the getter. Found answering item 5 of
self-audit e7184602. Tests/buttonmenus_spec.lua, red first. Also labelled
Blizzard's original, commented-out CALENDAR_FILTER_CVARS as not live, since
it has been misread as live twice.
One debug flag instead of two
The debug switch had two spellings, the DEBUG_MODE global and a debug field
on the ClassicCalendar table, kept in step by hand at four write sites. The
options loader in ClassicCalendar_Options.lua set only DEBUG_MODE, so /ch create's "Creating guild event" line and the /ch list login hint could
follow a different switch from the rest of the same command.
- Fix:
DEBUG_MODEis the only flag. The field and its writes are gone (ClassicCalendar.lua,Patches.lua/caldebug,ClassicCalendar_Options.luabox and loader), and its readers inCalendarHelper.lua,ClassicCalendar.lua,WorldBuffDB.luaandWorldBuffSync.luareadDEBUG_MODE./calwbsyncno longer prints the second flag's value. - Specs:
Tests/optionscheckbox_spec.lua"Debug mode has one switch": the options box turns the create line and the login hint on and off, and no file in the TOC mentions the old field.raidpicker_specandworldbuffdb_spectoggleDEBUG_MODEnow.
[v1.7.3] (2026-10-05)
WoW Forever: every holiday listed twice, some days without art
Files: Forever.lua. Call to Arms and Darkmoon Faire days had no art.
Operator's Forever screenshots (2026-10-05, thread d26b0d35): Hallow's End
starting twice (4:00 AM and 8:00 AM), two fishing contests every Sunday, four
"Call to Arms" lines every Tuesday. The Forever server sends its own holidays
through C_Calendar, and stubbedGetDayEvent appends ours after the
client's. Era and TBC servers send none. I never checked the Forever server
before claiming below that no client gives dates at runtime.
- Fix, Forever only:
Forever.luawrapsstubbedGetNumDayEvents/stubbedGetDayEvent. Server events stay first, soC_Calendar.GetHolidayInfo's index still names them. Our HOLIDAY entry with the same title on the same day is dropped, since the server owns its dates. An exact title match only. - Art: Forever draws the server's
iconTexture(wow-ui-source-foreverBlizzard_Calendar.lua:1662-1685), which the Call to Arms and Darkmoon days lacked. A server holiday with none borrows ours for the same sequence day, unless that art is switched off. - Description:
newGetHolidayInfofalls back toC_Calendar.GetHolidayInfo. - Cost: the merge is cached for one
CalendarFrame_UpdateDayEventscall only. - The Tuesday-only Call to Arms is not a date bug: Forever runs whole weeks, Tuesday to Tuesday (both copies said 10/13 - 10/20). Names show on first and last days only; the days between get the banner art.
- Tests: six in
Tests/forever_spec.lua. Not seen in game.
CurseForge now installs LibDBIcon for Classic Era and TBC players
Files: Libs/LibDBIcon-1.0.
Found by the harness's .pkgmeta checker during release prep. The embedded
LibDBIcon copy, which only the Forever TOC loads, carried a
--@curseforge-project-slug: libdbicon-1-0@ marker on its first line. The
BigWigs packager reads .pkgmeta's required-dependencies first. Then, while
copying files, it sets relations[slug] = "embeddedLibrary" for every marker
it finds (release.sh, copy_directory_tree), which overwrites the earlier
entry. So since the embedded copy arrived in v1.7.0, every upload has told
CurseForge that LibDBIcon is embedded. The CurseForge app then does not
install it, although ClassicCalendar.toc and ClassicCalendar_TBC.toc list
## Dependencies: LibDBIcon-1.0. A new Era or TBC player with no other addon
pulling LibDBIcon in would get no calendar. The marker line is removed; the
library code is unchanged. The checker now reports
libdbicon-1-0=requiredDependency. .luacheckrc also excludes Libs,
because vendored upstream code is not ours to restyle.
Battleground art sits behind other holidays' art
Files: ClassicCalendar.lua, Patches.lua, Forever.lua.
Operator, 2026-10-05: "as the BG ones are 'all the time' can we have their
art behind the other event art?" CalendarFrame_UpdateDayTextures drew the
FIRST holiday's sequence banner. So a battleground listed first hid another
holiday's banner, and a holiday with no banner of its own (the faire's middle
days) left the cell bare even with a battleground running. New
CalendarDayBanner (Patches.lua) picks the first holiday with a banner that
is not a battleground, and falls back to the first battleground. Events carry
isBattleground, set from the schedule's BattlegroundsArt; on Forever, the
server's copies are marked by name in the merge. Blizzard's own rule stays: no
banner for a sequence of 2 days or fewer. Three specs in
Tests/monthcache_spec.lua. Not seen in game.
Also checked: Forever's Call to Arms really is a week long. Its Holidays rows
1840-1842 (build 1.60.1.70205) carry Duration_0 = 168 hours on and
Duration_1 = 336 off, looping from 21 April 2026 08:00. Classic's weekend
rows 283-285 carry 100 hours.
WoW Forever: raid resets for its own raids (HolidayData.lua)
Operator, 2026-10-05: "do we not have raid resets in forever?"
GetClassicRaidResets returned nothing on Forever, by my own decision, until
Blizzard published lockouts. It now lists Barrow Deeps, Hyjal Summit and
Onyxia's Lair from their opening on 9 December 2026 (wowguide.net roadmap;
lfcarry.com). They are weekly, anchored on the client's weekly reset
(C_DateAndTime.GetSecondsUntilWeeklyReset) through the existing opens
handling, so the first shows on Tuesday 15 December. Weekly is an assumption:
Blizzard has published no lockout rules, and only a fan wiki
(world-of-warcraft-forever.wiki) reports weekly. Names come from
GetRealZoneText(249)/(534) where the client has them, not verified on
Forever, else English. holidayyears_spec now pins the three raids instead of
an empty list.
Added events cached per month: the viewed month and the one either side
Files: Patches.lua.
Operator, 2026-10-05, asking about micro stuttering: "you are only supposed to
cache the current month, the previou and the next, then that way it's warmed
and you look up once a month is scrolled". No such cache existed.
stubbedGetNumDayEvents/stubbedGetDayEvent re-derived every day's added
events from the schedule on every call, several times per day cell. v1.7.3
made that heavier: each call rebuilt GetClassicRaidResets, which now reads
C_DateAndTime.GetSecondsUntilWeeklyReset and, on TBC, names seven raids and
steps their anchors forward week by week. With the calendar closed none of
this runs (ClassicCalendar.lua:1343).
- Now: the added events (holidays, faires, battlegrounds, raid resets) are built once per month, in one pass. The cache keeps the client's current month and the one either side, which is exactly what one redraw reads, because the grid shows the edge days of both neighbours. Scrolling builds only the one new month; the other two are warm.
- Live, not cached: the client's own events (guild events, invites, the
Forever server's holidays) are read from
C_Calendaron every call. - Rebuilt when anything the month was built from changes: the holiday schedule (rebuilt daily), the day, a filter CVar, a holiday's art setting, or the level-up raid setting.
- The validity check allocates nothing. It runs on every lookup, about 250 per redraw. My first version built a signature string each time, which is the kind of garbage that makes the client hitch when the collector runs; it now compares values in one reused array.
- Tests: new
Tests/monthcache_spec.lua, 4 examples. Not measured in game.
Event dates checked against the game and published schedules, per client
Files: HolidayData.lua.
Every date the addon generates was checked against three sources, and the
results are recorded with links in docs/Event_Date_Sources.md. That file also
says how to get next year's dates. The three sources: each client's own
Holidays table (wago.tools CSV, decoded), the client API source in
F:\Blizzard API Docs, and published schedules. No client gives holiday dates
at runtime. Only the WoW Forever client's table carries real dates; Era and TBC
leave every date to the server. For 2025-2026, Forever's dates matched the
published Classic dates. Until now all three clients got Classic Era's data
unchanged. That was my design, and it was wrong for TBC and Forever.
- Dates corrected (all clients):
- Love is in the Air: was Feb 7-20, now Feb 11-15.
- Noblegarden: was a week, now Easter Sunday only.
- Children's Week: was May 1-7, now Apr 27 - May 4 in 2026. Game dates for 2025-2026, then the Monday on or before 1 May.
- Harvest Festival: was Sep 27 - Oct 4. It now starts 4 days before the
Mid-Autumn (moon) Festival, which reproduces all three of the game's dates
(2025 Oct 2, 2026 Sep 21, 2027 Sep 11). New
MidAutumn(year): published dates to 2035 (travelchinaguide, usemooncal), and a mean-new-moon fallback past that, measured within a day (9 of 11 table years exact). - Lunar Festival 2027: now Feb 2, where the Chinese New Year rule gave Feb 5.
- Battleground weekends: now end Tuesday 04:00 (100 hours), was the 07:00 reset.
- The game's dates live in a new
KNOWN_STARTStable and win over the rules. A moved holiday keeps its template length (templateSpanDays).
- Per-client schedules:
- New
CalendarFlavour()returnsera,tbc(interface 2xxxx fromGetBuildInfo) orforever. Forever can't be told from retail by build, soForeverScrollBars.lua, which only the Forever TOC loads, now setsClassicCalendar.isForever. - Template entries can carry
flavoursandportalgates. - TBC gains Brewfest, Sep 20 - Oct 6.
- TBC's Lunar Festival runs three weeks: Feb 16 - Mar 9 2026, by both Zygor's 2026 guide and Warcraft Tavern's TBC guide. Classic Era keeps two weeks. Later years are assumed to keep three weeks, which is inferred from 2026 alone.
- Forever puts Noblegarden on Easter Monday and adds Fireworks Spectacular on Jul 4, US realms only. Both come from its client table.
- Forever replaces Classic's battleground weekends with its whole
battleground weeks: Tuesday 08:00 to Tuesday 08:00, rotating Darkspear
Islands, Arathi Basin and Warsong Gulch, each one week on and two off.
These are marked
keepTimes. Recurrences now restore their hours, because a daylight-saving change between two recurrences moved the clock hour (found by the new spec). - TBC gains its raid resets:
- Karazhan, Gruul's Lair and Magtheridon's Lair from 19 Feb 2026.
- Serpentshrine Cavern and Tempest Keep from 14 May.
- Hyjal Summit and Black Temple from 27 Aug.
- All are weekly, with phase dates from the expcarry roadmap. A raid never shows a reset from before it opened, including when the client gives the anchor.
- Names come from
GetRealZoneText(mapID)in the player's language (ZoneScriptDocumentation.lua:27on TBC), with English as the fallback. The map IDs are from the TBC client's Map table. - Zul'Aman and Sunwell are left out until their dates are announced.
- Forever gets none of the Classic raids, none of which exist there. Its own raids are covered in the Forever raid-resets section above.
HolidayTextfalls back to English, because only enUS has Brewfest.
- New
- Season of Discovery's Darkmoon Faire runs every two weeks: new
SoDDarkmoonScheduleFor, Monday to Sunday every 14 days from Monday 13 January 2025 (Elwynn), alternating with Mulgore. It reproduces every 2025 date in zockify's SoD table and atlasforge's 2026 dates (21 September Elwynn, 5 October Mulgore, 19 October Elwynn, 2 November Mulgore). SoD used the monthly Classic faire before. The faire record is now built by one sharedfaireEntry. - Peer Review fixes (thread 2c750726):
- The client-anchored weekly reset showed an hour early.
CalendarTimehas no seconds, so the anchor landed 0-59 seconds before the reset and the forcedmin = 0turned 06:59 into 06:00. It now rounds UP to the next whole minute and keeps that minute. The reviewer suggested rounding to the nearest minute, but that still gives 06:59 past :30; the new spec, with the clock 45 s into a minute, caught it. The 52-week step back is now in calendar days, not seconds, so a daylight-saving change in between keeps the hour. - The holiday daylight-saving shift used the US rule for every region.
getDSTDatesandchangeWeekdayOfDateare replaced byrealmOnSummerTime, with per-region rules: US, EU (last Sunday of March to last Sunday of October), Oceanic (southern hemisphere), and none for KR/TW/CN. EU Hallow's End no longer ends at 22:59. Specs cover every region, including Oceanic, detected from "(AU)" in the realm name. - Fireworks Spectacular carries
keepTimes: its client-table time is shown as written, not shifted. Which clockHolidays.db2uses is not verified. - Noblegarden two-day bug: the new schedule-level spec was watched failing against the old 00:01 start (2026-04-04), then passing on the fix.
- The client-anchored weekly reset showed an hour early.
- Weekly raid resets come from the game:
C_DateAndTime.GetSecondsUntilWeeklyResetnow anchors the weekly raids' day and hour on every client except SoD. Before, the day and hour were guessed from the region. The EU-Wednesday and AU-18h guesses remain only as the fallback. The anchor is stepped back 52 weeks, becausePatches.luaprojects resets only forward of it. - Test environment:
cc.reset()now clearsClassicCalendar.isForeverandscrollBarTemplate. Before this, the Forever spec's flag leaked into every later spec file. .pkgmeta: all comments removed, per the packager rule. The reasoning they held is inCLAUDE.md.- Dead code removed from
HolidayData.lua:GetFollowingSunday, the unusedSoDBattlegroundWeekendstable,UBRSName, and whitespace-only lines. Nothing called any of them. - Specs:
holidayyears_spec: the 2026 table now holds the sourced dates, and the Noblegarden and Lunar 2027 examples follow the sources.holidaydata_spec: battleground weekends now end at 04:00.- New examples cover the game-table dates, the Children's Week rule, Brewfest on TBC only, Forever's Noblegarden, Fireworks (US only), the battleground weeks (Darkspear Islands on Oct 6 2026), Forever's raid list, and the weekly reset taken from the API.
- Resolved later in this release: TBC raids, SoD's fortnightly faire and TBC's three-week Lunar Festival are above. The 2026 Era battleground rotation matches zockify's 2026 dates. Forever's raid resets and its server-sent holidays have their own sections.
- Still not verified, on the todo list: TBC's Darkmoon Faire third site (Terokkar Forest), and TBC's Call to Arms rotation, which keeps the Classic one.
The Darkmoon Faire now appears in 2027 and later (HolidayData.lua)
- Cause, mine. The faire came from two hand-typed tables,
ClassicDarkmoonSchedule2025andClassicDarkmoonSchedule2026, andGetClassicDarkmoonsreturned the 2026 one for every year from 2026 on. Every entry carriedyear=2026, so from 1 January 2027 there would have been no faire on the calendar. It is the same defect v1.7.2 fixed for the seasonal holidays, and I left it in place then. - Fix. Both tables and
GetClassicDarkmoonsare replaced byDarkmoonScheduleFor(year). The Classic faire sets up on the first Friday of the month, opens the Monday after and runs to that Sunday; it is in Elwynn Forest in odd months and Mulgore in even ones. That rule is the one the 2026 table's own comment stated, and Icy Veins' and zockify's Classic schedules agree. It reproduces all twelve 2026 dates and eleven of the twelve 2025 dates; the 2025 table had January opening on Tuesday the 7th rather than Monday the 6th.GetClassicHolidaysnow adds the faires for last year, this year and next under the same one-year horizon as the seasonal holidays, so December also shows next January's faire, which it could not before. - Not verified: that Blizzard keeps this rule in 2027. As with the seasonal holidays, 2026 is the last year checked against the game. SoD still gets the Classic schedule, as before.
- Spec.
darkmoon_specgains aDarkmoonScheduleForblock. It must reproduce the shipped 2026 dates and zones to the day, open on a Monday in every month of 2025-2030, put a March 2027 faire on a 2027 calendar, and show January 2027 from 20 December 2026 with the schedule still under the 500-entry rebuild limit. The existing zone examples' nil check now goes throughassert(...).
Editor warnings cleared (no change for players)
v1.7.2 was tagged with 134 warnings in the editor's Problems panel; clearing
that panel is now a step in the release checklist in CLAUDE.md, along with
the Discord announcement, which was also missed. Every warning was read and
checked against the code or Blizzard's source before it was changed.
- One real slip, harmless:
ClassicCalendar.luapassedselectedAMas a second argument toGameTime_ComputeStandardTime, which takes onlyhouron Classic Era (Blizzard_Minimap/Classic/GameTime_Shared.lua:62) and Forever (Blizzard_FrameXMLUtil/GameTimeUtil.lua:56). It was always ignored; removed. CalendarTodayView.lua: twoOnMouseUphandlers shadowed the enclosingself(renamedarea); an unused..., loop index,selfand a deadcvarValue = "1"assignment removed; whitespace-only lines emptied. Behaviour unchanged.- Undeclared globals: the Today's Events XML frames
(
CalendarTodayViewFrameTitle,CalendarTodayViewHeaderBgDateText, ...) and the cross-file functionsCalendar_ShowandCalendarFrame_GetEventFrameare declared in.luarc.json, which.luacheckrcalso reads. - Annotation false positives:
UIDropDownMenu_AddButton(info, level)andDEFAULT_CHAT_FRAME:AddMessage(msg, r, g, b, id)are correct client calls (Classic EraUIDropDownMenu.lua:373,ChannelFrame.lua:376) that the editor's WoW API annotations declare with fewer parameters. Each is silenced on its own line with that evidence. Methods the specs stub (WorldBuffs:GetWorldBuffDataTable,EditEntry,CreateGuildEvent,WorldBuffSync:CatchUp) carry a line-scopedduplicate-set-fielddisable, andWorldBuffFrame = nilanassign-type-mismatchone. - Specs: nil reads go through
assert(...)instead of being indexed unchecked, so a nil fails naming what was missing; specs that stub client globals on purpose carry a file-levelduplicate-set-fielddisable with the reason. No example changed what it checks. - luacheck across the whole repo, for the first time. The desk's
lint_allhad been handing luacheck every file on one command line, which cmd.exe refused, so luacheck checked nothing; writ fixed that on 2026-10-05. Its first full run found four warnings in specs, now fixed.clientmodel_spechad two locals shadowing an outertext(renamedshippedanddocText),localization_spec's UTF-8 check had accshadowing the env module (renamedcont), andwbconvergence_spechad an unusedbob..pkgmetaloses its"*.bat"ignore entry, which matched no file in the repo.
[v1.7.2] (2026-10-03)
WoW Forever: "Unable to load atlas entry UI-ClassicScrollBar-Center"
LibAceGUIWidgets thread 3ee3aa5b.
Reported from the Forever client (BugSack, 24x and then 28x):
Blizzard_SharedXML/Shared/Scroll/TrimScrollBar.xml:337 Texture Texture: Unable to load atlas entry UI-ClassicScrollBar-Center.
- Cause, mine. The calendar's four scroll bars (View Event and Create
Event descriptions, the event picker, the texture picker) inherited
Blizzard's
WowClassicScrollBarinClassicCalendar.xml. Its middle track art is that atlas (wow-ui-source-foreverTrimScrollBar.xml:337), which Forever's client does not have. v1.7.1 restored the template after v1.7.0's per-flavour alias broke Classic Era, and I first declined this report as unfixable. That was wrong; the operator overruled it. - Why not
Forever.lua: the atlas is applied only while the template is built, which happens duringClassicCalendar.xmland before any file loaded after it. - Fix. The four bars are no longer declared in
ClassicCalendar.xml. Each owning frame'sOnLoadbuilds its bar with the newCalendarCreateScrollBar(ClassicCalendar.lua), using the same anchors, strata and hidden background as the XML had. The template isClassicCalendar.scrollBarTemplate or "WowClassicScrollBar", the same pattern as Blizzard'sScrollFrame_OnLoad(SecureUIPanelTemplates.lua:44). NewForeverScrollBars.lua, loaded byClassicCalendar_Camelot.toconly and beforeClassicCalendar.xml, sets it toMinimalScrollBar, which Forever's own calendar uses for the same four bars. Classic Era and TBC keepWowClassicScrollBar, and their TOCs are unchanged. - Specs.
forever_specbuilds the real XML both ways. Each of the four bars must come fromWowClassicScrollBar(Era/TBC load) orMinimalScrollBar(Forever load). Whether a bar has theInitthat v1.7.0 lost on Era cannot be checked offline: the harness builds Blizzard templates without their mixin Lua, soInitis nil there on every route (my first version of the spec asserted it and went red on both flavours).tocorder_specnow pins that the shared XML declares none of these bars, that only the Forever TOC loadsForeverScrollBars.lua, and that it loads it before the XML. - Tested by the operator in the Forever client (2026-10-03): "i'm in forever, no more lua errors, addon still works" (relayed on thread 3ee3aa5b). Still unverified in game: that the bars look and scroll as before on Classic Era and TBC.
Seasonal holidays now appear in 2027 and later (HolidayData.lua)
Found during the documentation check below; the defect is mine.
- Cause.
CLASSIC_CALENDAR_HOLIDAYSheld fixed 2026 dates (plus Winter's Veil 2025-26), andGetClassicHolidaysused them as they stood. From January 2027 nothing from Lunar Festival through Winter's Veil would have shown. The Easter and Chinese New Year helpers in the file were never called, and the Lunar Festival ones used +/-7 days, which matched none of the shipped data. - Fix. The table is now a 2026 template, and
SeasonalHolidaysFor(year)derives each year from it, using the rules the 2026 data itself follows. Noblegarden starts on Easter Sunday (GetEasterDate) and runs a week. Lunar Festival starts the day before Chinese New Year and ends 13 days after it, using a table of published dates for 2025-2035 (chinatravel.com, vclock.com), with the moon-phase estimate as the fallback beyond that. Every other holiday keeps its month, day and times. The schedule builds last year, this year and next, so last year's Winter's Veil still shows in early January, and drops anything starting more than a year from today: the same one-year horizon the recurring events use, whichholidaydata_spec"keeps recurring events inside the one-year rolling window" enforces. My first version added the whole of next year and failed that example on Harvest Festival. The deadGetLunarFestivalStart/Endare replaced, and the separate Winter's Veil 2025-26 entry is gone because the derivation produces it. - Not verified: that Blizzard runs these holidays on the same rules in 2027. The 2026 dates were the only ones checked against the game; later years are the same pattern carried forward.
- Not changed: the Darkmoon Faire still uses the 2026 schedule for every
year from 2026 on (
GetClassicDarkmoons). - Spec. New
Tests/holidayyears_spec.lua. The 2026 derivation must reproduce the shipped 2026 dates to the day. Easter-based and Chinese New Year-based dates are checked for 2027-2030 against published dates. A 2027 clock must produce a 2027 Lunar Festival, and 1 January 2027 must still show Winter's Veil 2026.
/calwbclear guild asks first; the dead Fireworks Spectacular option is removed
/calwbclear guild(Patches.lua) wiped the whole guild's world-buff data immediately, while the README promised a prompt. The bare word now prints what it will do, and/calwbclear guild confirmdoes it: the same two steps as the personal/calwbclear confirm. NewTests/calwbclear_spec.luapins both halves with a stand-inWorldBuffSync.WipeGuild.- The Fireworks Spectacular art checkbox (
ClassicCalendar_Options.lua) is deleted. It was gated onisSoD, whichPatches.luasets two files later in the TOC, so it was never built on any client; and no holiday inHolidayData.luausesFireworksSpectacularArt. Its default (CCConfigkey) and locale strings are left alone.HolidayData.lua's moon-phase fallback no longer reassigns one local to a different type (language-servercast-local-type).
Documentation brought in line with the addon, again
README.md, docs/Curseforge_Description.html and
docs/Curseforge_Description.md were checked against the code once more, and
they still claimed things the code does not do. Each item was read in the
source before the docs were changed.
- The World Buff window is officer-only.
/worldbuffreturns before building the window for a non-officer (WorldBuff.lua:1660-1663), and the Add Events entry falls back to that command. The README said "everyone can view". /calwbclear guildhas no confirmation prompt. It callsWorldBuffSync:WipeGuilddirectly (Patches.lua:813-816). Only the window's Clear All (Guild) button asks first (WorldBuff.lua:558-583)./calwbclear confirmclears every world-buff table saved on the account, not only "your own".- There is no Fireworks Spectacular holiday.
HolidayData.luahas no such entry. The art toggle inClassicCalendar_Options.lua:517is behindisSoD, which is not yet set when that file loads (it is set inPatches.lua, two files later), so the toggle is never created. Removed from the HTML in two places. - The seasonal holidays carry fixed 2026 dates (
HolidayData.lua:289onward). The HTML said "generated for the current year" and "accurate 2026 dates ... and more". Corrected, and fixed in the section above. - The tracked world buffs are exactly four (
WorldBuff.lua:19-24), not "and more". - The Raid Events picker is officer-only (
ClassicCalendar.lua:2320-2326) and lists CalendarHelper's saved raid events. - Added where missing: Shift+click on Add Events opens Today's Events;
the Templates menu deletes templates and adds the Dibs loot line; Rank
Auto-Confirm can be set while creating an event; the undocked World Buff
window remembers its spot; untranslated option text falls back to English;
the options' Discord link button; the "hide level-up raid resets" option
shows everywhere but only has an effect on Season of Discovery;
/ch list,create <number>andstatus.
[v1.7.1] (2026-09-30)
Event reminders: hour units and invite statuses (Patches.lua AlarmUpcomingEvents)
Found while checking the documentation against the code; both defects are mine.
- An "N hours" reminder fired N minutes before the event. The options
panel saves the unit as
"minute"/"hour"(ClassicCalendar_Options.lua), and the reminder compared against"Minute"/"Hour", so the hour branch never matched and the multiplier stayed at 60 seconds. The comparison is now case-insensitive. The fully localized 15-minute message (CALENDAR_EVENT_ALARM_MESSAGE) had the same mismatch and was never used; it is now. - Reminders went to the wrong invite statuses.
alarmInviteStatuseswas a list indexed by position, and the check readalarmInviteStatuses[event.inviteStatus]. With Classic Era'sEnum.CalendarStatus(Available 1, Declined 2, Confirmed 3, Out 4, Signedup 6, Tentative 8;CalendarConstantsDocumentation.lua), that matched Available, Declined, Confirmed and Out, and never Signed up or Tentative. It is now a set keyed by status value. - Spec: new
Tests/eventalarm_spec.lua(10 examples) loadsPatches.lua, fixes the clock, stubs one player event and firesVARIABLES_LOADED, then reads what reached chat. - Not changed, noted: the reminder only looks at today's events, so an hours reminder for an event early tomorrow does not fire the evening before.
Documentation brought in line with the addon
README.md and the CurseForge description were checked claim by claim against
the code. Corrected: the filter list (there is no player/guild-event filter;
Darkmoon Faire, weekly holidays, raid lockouts and raid resets were missing);
the options layout (art toggles, guild settings and event alarms are sections
of the General page, not separate tabs); /caldebug is available to everyone,
not officers only; "itIT" removed from the locale list (no Italian table
exists); the world-buff title rule ("Dropping" plus the buff, or "World
Buff"); "placeholder entries" are a Create Placeholder button that
schedules a TBD event. Added: the minimap icon's clicks, unlocking/hiding the
calendar button, the Season of Discovery level-up raid reset option, Request
Guild Settings, /ccwb log and reset, /calwbclear confirm and status,
and three known issues the README lacked. docs/Curseforge_Description.md got
the same corrections (its keyword list, "Art tab" and filter line were also
stale), and README, the HTML and the .md now carry the same known issues:
the Burning Crusade holiday-date caveat and the guild event invites broken
since 1.15.3 were each missing from one or two of them.
Classic Era and TBC: the View Event window raised a scroll-bar error
A Forever change of mine that reached every flavour.
Reported from the client (BugSack, 16x):
ScrollUtil.lua:127: attempt to call a nil value in Init, from
CalendarViewEventDescriptionContainer_OnLoad (ClassicCalendar.lua:3805).
- Cause, mine. v1.7.0 routed the calendar's four scroll bars through an
addon template,
ClassicCalendarScrollBar, defined per flavour (ScrollBarClassic.xmlfor Era/TBC,ScrollBarForever.xmlfor Forever), to quiet a missing-atlas warning on Forever. The operator's Forever work was the 1px border gap (Forever.lua); the scroll-bar change was a side change of mine, and because it lived inClassicCalendar.xml, which every TOC shares, it shipped to Classic Era and TBC. There the View Event description's bar had noInitwhenScrollUtil.InitScrollBarran. The operator confirmed the revert in the Classic Era client: no error. Only that container callsInit; the Create Event one only registers, which is why it alone raised. Why the client built the bar without its mixin is not established -- the Era 1.15.9 source defines every template in the chain. The suite built it offline without complaint, so the harness does not reproduce it. - Fix. The four bars inherit Blizzard's
WowClassicScrollBardirectly again, exactly as v1.6.8 shipped.ScrollBarClassic.xmlandScrollBarForever.xmlare deleted and gone from all three TOCs and from the eight specs that loaded them.tocorder_specnow pins every scroll bar inClassicCalendar.xml(except the Today View's ownMinimalScrollBar) toWowClassicScrollBar, andCLAUDE.mdrecords that a Forever-only change never goes in the shared XML. - Known cost on Forever: the four "Unable to load atlas entry UI-ClassicScrollBar-Center" warnings come back there, because Forever's client lacks that one piece of the bar's art. Fixed in v1.7.2.
Test suite: the per-example reset no longer keeps old copies of the addon
WoWAPITesting, thread c0e6ec5b. Every old copy had stayed alive.
Development only; nothing a player runs changed.
- Libraries the addon registers into are now reloaded on every reset.
WoWAPITesting measured the suite's heap climbing from 7 MB to 270 MB with
the frame count flat. The cause was mine.
Tests/env_cc.luareset the addon for every example but kept AceComm and VersionCheck-1.0 loaded, and each reload registered into them again. VersionCheck arrives throughsyncstartup_spec's DeltaSync load and then serves every later reload ofClassicCalendar.lua:192. So every example's copy of the addon stayed reachable.cc.reset()now forgets DeltaSync, AceCommQueue, VersionCheck, AceComm, AceSerializer and ChatThrottleLib. Each spec already loads the ones it needs right after the reset. - The hooked calendar functions no longer stack up.
installGlobals()kept an existingCalendarFrame_ShowEventFrame(X = X or stub), so each example hooked the previous example's already-hooked function.CalendarCreateEventFrame_Updatestacked up the same way throughRaidTemplates.lua. Both are now cleared on reset. - Harness pin moved
f7742d1->544b409. Nothing in the Adoption log in between needed a change here. - Suite: 826 passed, 0 failed, one process, 120 s. WoWAPITesting read the run's telemetry: the heap now levels off at 22-41 MB and ends at 34 MB, where the previous full run reached 272 MB. Live frames stay around 6,000-8,400 instead of climbing.
[v1.7.0] (2026-09-27)
Raid scheduling: sign-ups, extra statuses, raid templates and the Dibs link
Contract DIBSREQ-CC-002.
Operator, 2026-09-26, through Dibs: raid scheduling, templates and sign-ups "belong in the clanedar", with "links to the right SR in dibs in the invite" and "New statuses in CC ouside of what the server tracks, synced with deltasync and canon hashes". Dibs owns the loot systems and READS what is built here.
- Sign-up capture and sync (
RaidSignups.lua,RaidSignupsSync.lua). Every client records its own answer to each upcoming guild event (GetGuildEventInfo), and whoever opens an event captures its whole invite list with class, note andEventGetInviteResponseTime. The merged roster rides the EXISTING DeltaSync host as one extension item,rs1:index(WorldBuffSync:RegisterExtension), not a second namespace. A player's row is split into records whose winner is decided whole --obs,first,cls,note,extra-- so each keeps the canon its author minted (DeltaSync's canon rule); a field-level merge would produce rows no client authored. Event keys aretostring(eventID)and never converted back (a value above 2^53 loses precision). Captures wait for LibGuildRoster to be ready, so a half-built roster cannot key one player two ways. - The sign-up number never moves. It ranks by the EARLIEST response time ever seen for that player on that event, compared as numbers; no extra status and no re-answer changes it.
- Extra statuses: late, tentative, bench, absent. Member-owned (a record
is accepted only from the player it is about), newest wins. A strip docked
under the open event window (
RaidEventBar.lua) sets your own and, on hover, lists who holds which. The strip sits OUTSIDE the event windows because both are full; nothing of Blizzard's layout moved. - Raid templates (
RaidTemplates.lua). A Templates button on the Create Event window, shown only when creating a guild event (where Mass Invite is hidden, so they never overlap), opens a menu: apply a template, save the form as one, delete one, and pick the Dibs loot system and raid. A template fills title, type and instance picture, time, description and lock; the leader presses Create (AddEventis restricted). No size (Classic Era has noEventSetSize), no day (the leader clicks the day), no recurring nights (each still needs the leader's own Create press). Templates are the leader's, account-wide, never synced. - The Dibs link. The description ends with one line,
Loot: <label> [cc:<system>:<raid>]. The whole description stays inside the box's 255-letter limit, and it is the rules text that gets shortened, never the loot line, and never mid-way through a UTF-8 character. When the open event carries the line and Dibs is loaded, the strip shows Loot in Dibs, which callsDibs.API.Open(system, raid). Feature-detected by name; no dependency on Dibs. - Read API for Dibs:
ClassicCalendar.API.VERSION = 1,UpcomingRaids(),Signups(key),LootLink(desc), andRaidsChanged/SignupsChangedcallbacks. Everything returned is a copy.templateIdis not inUpcomingRaids, although reply f0056fd5 named it: templates are never synced, so no reader's client could resolve one. - Three defects of mine, found while building this:
- A clear could lose to the status it cleared. Stamps are whole seconds;
Late then None inside one second tied, and the canon tie-break kept
whichever hashed higher.
RaidSignups.SetExtranow stamps a new choice after the one it replaces. Mutation-verified. - The strip's frame replaced its own module. It was created as
CreateFrame("Frame", "ClassicCalendarRaidEventBar", ...), and a named frame is a global, so building it overwrote the module table of the same name. RenamedClassicCalendarRaidEventStrip; pinned by a spec. - The Create Event window kept the last event's description.
CalendarCreateEventFrame_UpdateclearedCalendarCreateEventDescriptionContainer.CalendarCreateEventDescriptionContainerScrollingEditBox, a key nothing sets (the container's OnLoad sets.ScrollingEditBox), so the box kept its old text while the event itself was reset to empty. What the leader saw was not what Create would send. Mutation-verified.
- A clear could lose to the status it cleared. Stamps are whole seconds;
Late then None inside one second tied, and the canon tie-break kept
whichever hashed higher.
- Specs:
raidsignups_spec(38),raidsignupssync_spec(28) and the newraidtemplates_spec(26: the loot line, time conversion, the store, the real Create Event window, the strip). The client globals the create window reads, and theScrollingEditBoxTemplate/ScrollingFontTemplatemixins, are now modelled by the harness (WoWAPITesting 7f2fd3f, bf8d140); the stand-ins that were staged here for them are deleted. - Suite runs in one process. The "15-minute limit" I first reported was the desk's default test timeout, not a harness limit. WoWAPITesting then found the harness kept every frame ever built alive (d77ed4f); on the current pin the whole suite runs in about 2.5 minutes. Release run: 826 passed, 0 failed.
Create Event times: noon stayed noon, and the Dibs line is plain text
- Switching the Create Event window from 24-hour to 12-hour time moved a
12:30 PM event to 12:30 AM, a defect of mine. Blizzard's
GameTime_ComputeStandardTimereturns AM for hour 12 (GameTime_Shared.lua:62, Classic Era), and the format switch took both the hour and the AM/PM flag from it. The 12/24-hour conversion is now one shared function (it was in three places), used by the switch and by raid templates (ClassicCalendar.lua:4966,RaidTemplates.lua:174). Mutation-verified inraidtemplates_spec("keeps 12:30 at noon"). - The Dibs loot line in event descriptions is ASCII only
(
RaidTemplates.lua:119), the rule CalendarHelper already applies to text it writes, so a translated loot label can never put characters in a description that the import path strips.
Auto-Moderator: a name with no realm is your own realm, not any realm
Peer Review, thread 21985b78.
Measured by Peer Review on the operator's Old Blanchy client: a same-realm
calendar invitee arrives as a bare name, and the client spells a realm two
ways (GetRealmName "Old Blanchy", GetNormalizedRealmName "OldBlanchy").
- Wrong player promoted, a defect of mine.
AutoModerator.NamesMatchlet a bare name match that name on ANY realm. In a connected group, a saved "Bob-Azuresong" would promote a different, bare "Bob" from the leader's own realm, and a bare "Bob" the leader typed matched every Bob. A missing realm now means the viewer's realm, on both sides. Mutation-verified. - One realm rule.
GuildMemberSearch.NormRealmremoves spaces AND apostrophes (the matcher used to drop only spaces, so "Quel'Thalas" never matched "QuelThalas"); the newGuildMemberSearch.OwnRealm()is the viewer's realm through it, used by the matcher and byRaidSignups.PlayerKey. The matcher deliberately does not go throughPlayerKey: that resolves names through the rosters, which would turn a bare local "Bob" into a sister guild's "Bob-Azuresong". - Removing a bare name.
Remove("Bob")takes the leader's own-realm Bob first, then the only stored Bob. With two other realms' Bobs it removes neither and the message names them and asks for the realm. The right-click menu used to print "removed" whether or not anything was. - Removing from the right-click menu could remove nothing.
IsListedmatched a saved bare "Bob" for an invitee shown as "Bob" plus your own realm, butRemovetried only the exact key, so the menu offered Remove and then printed "not on your list".Removenow takes out every entryIsListedwould match. Specs cover the menu message both ways. - An unknown own realm no longer guesses (self-audit 9af0d144, D). While
the client returns no realm name, a bare name matches no realm-qualified
entry (the next invite-list update tries again),
RaidSignups.PlayerKeygives a bare name no key instead of minting"Ana-", a one-player sync push it cannot key sends nothing instead of every row, and the open-event capture waits. Not seen in a client: whether the realm is ever unknown at those moments. RaidSignupsno longer wrapsGuildMemberSearch.NormRealmin a local forwarder; it calls it directly.- A cross-realm invitee's spelling in the invite list is still unmeasured; nothing depends on it.
Closing the suggestion menu no longer reads a private library field
GuildMemberSearch.CloseMenuFornow calls LibAceGUIWidgets' publicW:CloseMenuFor(anchor, ...)(MINOR 31, built for us on contract thread 4d698ce3) and no longer readsW._menuAnchor(v1.6.8). A library older than 31 gets the unconditional close. The "never closes somebody else's menu" examples innamesuggestions_specpass against the installed MINOR 36.
WoW Forever support
Operator, 2026-09-27: players report no calendar on WoW Forever, and there is
none in game. Forever's client carries Blizzard's calendar, but a server game
rule turns it off: Blizzard_Minimap/Mainline/GameTime.lua:31 disables the
clock button under Enum.GameRule.IngameCalendarDisabled, and /calendar
returns early on the same rule. So Forever is the Era/TBC case, not the Wrath
one.
- New
ClassicCalendar_Camelot.toc, Interface 16001, same file list as the other TOCs. It loads the embedded LibDataBroker/LibDBIcon instead of requiring the standalone, which does not list 16001 (TOGTools contract 2b570b7d, step 5), soLibs/LibDBIcon-1.0is kept for it. wow-version-replication.ps1now also copies to_classic_beta_, the Forever install.tocorder_specaccepts the_Camelotsuffix, checks Forever's embedded load order and its 16xxx interface, and allows only those two library lines to differ between TOCs.- Needs CalendarHelper on Forever (a required dependency): delivered by CalendarHelper on thread fc85d148.
- Creating an event raised "attempt to call a nil value"
(ClassicCalendar.lua:5869) on Forever.
SetDesaturationis a Blizzard global in Classic Era (Blizzard_UIParent/Shared/UIParent.lua:546) that Forever's client does not have.ClassicCalendar.luanow carries its own local copy, a wrapper fortexture:SetDesaturated, which every client has. Found by the operator in the Forever client. - Four "Unable to load atlas entry UI-ClassicScrollBar-Center" warnings on
Forever. The calendar's four scroll bars inherited
WowClassicScrollBar, whose middle art Forever lacks. They now inheritClassicCalendarScrollBar, defined per flavour:ScrollBarClassic.xml(Era/TBC, stillWowClassicScrollBar) andScrollBarForever.xml(MinimalScrollBar, what Forever's own calendar uses at the same four places). The seven specs that build the calendar XML load the Classic file;tocorder_specpins each TOC defining the template beforeClassicCalendar.xml. - The dark window borders' right edge sat 1-2px outside the corners on
Forever (operator's screenshot;
/fstacknamesCalendarViewHolidayFrameBorder). Blizzard's "Dialog" nine-slice layout is the same code in both clients, so the difference is Forever's atlas art. NewForever.lua, loaded by the Forever TOC only, re-applies a COPY of that layout with the right edge moved in on our seven dark-bordered windows. Classic Era and TBC never load it; Blizzard's own layout is not modified. UNVERIFIED: the first in-game try showed no visible change.RIGHT_EDGE_X(-1 UI unit) is a judgement from a screenshot, not a measurement;ClassicCalendar.ForeverBorderReport()prints the real edge and corner positions so it can be set from numbers, and is to be deleted once it is. - New
Tests/forever_spec.lua(self-audit 9af0d144): builds the real calendar XML, removes theSetDesaturationglobal and opens Create Event's class buttons (mutation-verified: without the local copy it raises the exact Forever error at ClassicCalendar.lua:5876), and runsForever.luaagainst the real windows -- a renamed window or a lost Dialog border turns it red instead of silently skipping. - Tested by the operator in the Forever client (2026-09-27): the addon
loads, the calendar opens, Create Event works after the
SetDesaturationfix, and the scroll bar warnings are gone. Still open there: the border edge above, and scrolling a long description or the pickers with the new scroll bar.
Addon list title in the fleet's brand colour
- Both TOCs'
## Titleis now|cffFF8000Classic Calendar - Revived|r, the orange every one of our addons uses (DeltaSync thread dd94f8d6). No version tag and no## Category: this is not a library.
LibDBIcon-1.0 is a required dependency, not an embedded copy
TOGTools contract 2b570b7d.
An embedded LibDBIcon only updates for players when this addon is re-released; as a CurseForge required dependency it updates for everyone on its own.
- Both TOCs list
LibDBIcon-1.0in## Dependenciesand no longer loadLibs/LibDBIcon-1.0;.pkgmetalistslibdbicon-1-0underrequired-dependencies. The standalone addon brings LibDataBroker-1.1 with it. No Lua changed:LibStub("LibDBIcon-1.0")resolves the same. - The Era and TBC TOCs no longer load the embedded
Libs/LibDBIcon-1.0. The folder stays: the WoW Forever TOC (below) still loads it, as the contract's step 5 directs. - Known cost: a player who installs by hand must also install LibDBIcon-1.0.
minimapbutton_specloads the installed standalone library first, as the client does for a dependency. LibDBIcon capturesMinimapwhen its file loads, so the spec brings up the harness's frame layer before the first load.- The README and CurseForge page list LibDBIcon-1.0 with the other required libraries.
The "Hide the calendar minimap button" option came undone at every login
Reported by a player on Discord, 2026-09-22: "Every time I login the minimap button is there, and I have to deselect and reselect the option to get it hidden again." Two defects of mine, and they compounded.
- The option was applied before the button existed.
CCOptionsHandlercalledLibDBIcon:HideatADDON_LOADED; the button is registered atPLAYER_LOGIN(ClassicCalendar.lua), so the Hide found nothing. LibDBIcon'sRegisterthen shows the button unlessdb.hideis set (LibDBIcon-1.0.lua:321-327), and itsHidenever records anything (:409-414) -- so nothing ever set it, and the button came back every login. - The LibDBIcon db was never saved. It was
local ClassicCalendarDB = ClassicCalendarDB or {}at file scope, which runs before the client restores saved variables, so it bound a fresh table every session. The globalClassicCalendarDB(declared in both TOCs) was never assigned by this addon at all -- which also means the button's dragged position has never survived a login. - Fix: one function,
ClassicCalendar.SetMinimapButtonHidden(hidden), records the choice inCCConfigAND inClassicCalendarDB.minimap.hide(the saved variable, created atPLAYER_LOGIN), then hides or shows the button. The login path copiesCCConfig.HideMinimapButtoninto the db beforeRegister, so the button is created hidden. The options check box calls the same function; the deadADDON_LOADEDhide is removed. - New
Tests/minimapbutton_spec.lua, 5 examples, on the whole-addon load with the real LibDBIcon (now the standalone dependency, see above) and a realPLAYER_LOGINbroadcast (so LibDBIcon's login frame and ours run in their real order): hidden stays hidden at login, shown when off, the db IS the saved variable, a choice carried over through the saved variables holds on the next login, and a dragged position survives. Mutation-verified: removing the pre-Registerdb.hideline turns the reported case red. Suite 713.
Older versions (v0.7.0beta to v1.6.8) are in CHANGELOG_ARCHIVE.md.
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include