Questbook-v0.1.7
What's new
Questbook Changelog
[v0.1.7] - 2026-10-20 - Multi-hop flights are one flight
A flight through several flight masters is guided as one flight (2026-10-19)
- TOGProfessionMaster reported (inbox 4951ee1a, from a player on its Discord) that a guided route
"sometimes has you stop at each flight point". QuestDB's flight links join only flight masters
with a direct path (Aerie Peak links to its five neighbours,
Data/Vanilla/_core/Paths.lua), andRouter:Routemade one leg per link. So the HUD named the first tower on the way, the player picked it on the taxi map, and they landed at every one.Routenow folds consecutive flight hops into one leg that is named for, and ends at, the last landing.PlannedLanding,NextRideand the in-air steer all read that leg, so they name the final flight master. The client picks its own chain to the chosen tower, which need not be the one planned; only the drawn middle of the line and the time estimate can differ. Router:SearchchargedFLIGHT_BOARDon every hop, which over-priced long flights against walking. It now searches over (node, arrived by flight) states and charges the board once per flight.- In the air on a merged flight, the HUD's yards and ETA measured a straight line to the final
landing, which reads short on a route that bends at a tower and barely closes on a hop flying
sideways to it (Peer Review, inbox 7c9c5115).
Router:Remainingnow sums along the flight's own points from the one ahead. That point comes from a newRouter:AloftAhead, whichTracker:Pathnow shares, so the drawn line and the yards agree. It picks the end of the segment nearest the player: the first cut took the point after the nearest point, which past the halfway mark of a hop skipped the tower still ahead and cut the corner. Only segments wholly on the player's continent count, asRouter:Aboardalready did: a flight crossing continents has points in two frames, and the other frame's can lie nearer in raw yards. A player already past the nearest segment's end is flying the next one, which is how the last stretch before a crossing (left out by that rule) still points at the far tower. KNOWN COST: a chain that crosses its own path is still ambiguous where it crosses; the nearest segment there may be the other pass. Tracker:CheckBoardedcompared the flight taken against the planned one with a bare prefix test. A flight master spelled differently in the graph and the client (an alias, or an apostrophe) counted as the wrong flight, and the route was thrown away and planned again from the landing. It now goes throughRouter.TaxiNameMatch, the matcherFlightMasterNamedalready uses.Tests/router_spec.lua: a four-tower chain flies as one leg, boarded once; a flight after a walk still pays the board; the bent-flightRemaining; a graph a spec loads does not surviveenv.reset; and a differently spelled boarded flight keeps the route. The in-air spec's remaining-yards figure now follows the curve.
[v0.1.6] - 2026-10-07 - LibDBIcon installs on Era and TBC, and a quest list for Grouper
LibDBIcon no longer marked as embedded, so the CurseForge app installs it on Era and TBC (2026-10-07)
- TOGBankClassic found (inbox e90504be) that the bundled
Libs/LibDBIcon-1.0/LibDBIcon-1.0.luastill carried--@curseforge-project-slug: libdbicon-1-0@. The BigWigs packager greps every packaged.luafor that marker and records the slug as an embedded library, which overrides.pkgmeta's required dependency (release.sh:1882-1889).Questbook.tocandQuestbook_TBC.tocdo not load the bundled copy (onlyQuestbook_Camelot.tocdoes), so a CurseForge-app install on Era or TBC got no LibDBIcon at all. The marker line is removed. The copy stays bundled for WoW Forever only and is a required download everywhere else, as designed. - The file also gets a
---@diagnostic disableheader. It is vendored upstream code, and the language server's type warnings on it are not ours to fix.
Questbook:GetActiveQuests() for other addons (2026-10-07)
- Grouper asked (inbox 62ba8d99) for a public read of the live character's quest log for its
Create form's quest picker.
Questbook:GetActiveQuests()(Core.lua) returns, in log order,{ questId, name, level, zone }per quest: headers and id-less entries are left out,nameis the log's (client language),levelthe log's or else the database's, andzoneisQuestData:GetQuestSummary(id).zoneNameor nil. It reads throughQuestLog:Entries, so it follows the Active tab's per-flavour path (C_QuestLog on WoW Forever, the Vanilla globals elsewhere). - It never raises. The log read and every database lookup are pcalled, and a failed lookup costs only that quest its database fields. It returns an empty array before the addon initialises, on an empty log, or when the log read fails. While QuestDB is still loading or absent, it still returns the log, without zones.
- New
Tests/activequests_spec.lua(20 examples).Tests/core_spec.luanow also covers/questbook missing's two untested replies: database not loaded, and nothing missing.
Test suite: harness at b20754a, one garbage collection per example (2026-10-07)
- Harness pinned to
b20754a(from85c2946). - Deferred collection (harness
515873b, inbox fa45bde6): the env turnswow.deferCollection(true)on once.M.resetnow releases the previous load first (newM.release(): every Ace3 module, the widget, minimap, guild-sync and VersionCheck libraries, LibQuestDB, Questie's stand-in andQuestbookDB), then runs onewow.collect(), and asserts nothing is left pending. Each example now takes one full collection instead of two. - This is the release that v0.1.5 measured and backed out, because something from
guildsync_speconward stayed live. At this pin (harnessd640d37silences an evicted Ace3 library's frames), the heap and live frame count after every file match the old run, andretention_specpasses. - The six
_G.AceComm30Frame = nillines inTests/guildsync_spec.luaare gone (inbox 050f3f32). Every example there reloads AceComm throughace.forgetorace.reset, which now clear that global themselves. - The manual
wow.menuModifiersclearing beforeframes.reset()stays. Harness303bc99replaces a registration only when the same line registers again, and until the reloaded TrackerMenu does, the old callback would keep the previous load alive through the collection. - The Lua language server's settings now live once, in
.vscode/settings.json, which VS Code reads whether it opens the folder orQuestbook.code-workspace(everyLua.*setting is resource-scoped). They used to sit only in the workspace file, so opening the folder directly read a stray short globals list and flagged about 1,600 false problems.ExpandSkillHeaderandGetQuestLogPushablejoined the globals list. A.luarc.jsonwas tried first and dropped: under it the WoW API annotations stopped loading, and every built-in type read as undefined.
[v0.1.5] - 2026-09-30 - The accept warning beside Dialogue UI
The accept warning sits beside Dialogue UI instead of going to chat (2026-09-30)
- A player reported that with Dialogue UI on, the breadcrumb warning went only to chat. Dialogue UI
unregisters every
QuestFrameevent at load (YUI-DialogueCode/Core.lua:420) and shows its ownDUIQuestFrame, so the panel, a child ofQuestFrame, never showed and Questbook fell back to chat.AcceptWarning.Hostnow picks the dialog on every offer:QuestFramewhile it takesQUEST_DETAIL, otherwiseDUIQuestFramewhen Dialogue UI is loaded, otherwise chat as before. Beside Dialogue UI the panel takes that frame's DIALOG strata, sits on the side facing the middle of the screen (by the sign of itsframeOffsetX), and hides when that frame closes. - The host used to be decided once, at install. Dialogue UI's "disable in instances" setting hands
QuestFrameits events back on entering an instance, so a decision made at login went stale. - The chat lines' wording is unchanged, so anything matching "Questbook: accepting this quest closes" still works whenever the warning falls back to chat.
- Spec: the chat-fallback case now counts this load's own prints. The harness's frames survive a reset, so listeners from earlier loads in the same Lua state also hear the event and print.
Test suite: retention measured, the slow-suite cause partly found (2026-09-30)
- WoWAPITesting flagged this suite as the fleet's slowest (inbox fa49c49a): 252 s of a 345 s run were full garbage collections, two per example, over a heap of about 150 MB.
- New
Tests/retention_spec.luaasserts that a reset releases the previous load: plain, with QuestDB's real data, and with the guild sync's libraries. It also asserts that the live heap settles within 5 MB of a plain reset's. When a load survives, it names the reference path that holds it. All four pass. A settled reset holds about 5 MB, so the 150 MB is not a per-example leak. - The capture of QuestDB's real Vanilla data (about 15 MB) is now kept only while consecutive resets ask for it.
- Found:
wow.reset()andframes.reset()collect BEFORE the env reloads anything, while the previous example's whole load is still reachable through LibStub. Releasing it first (ace.reset,libs.reset, evicting QuestDB) cut each collection from about 275 ms to about 80 ms up toguildsync_spec. From there on, something stayed live for the rest of the run (233 MB and 18,600 frames, against 157 MB and 11,500), and the whole run went from 380 s to 442 s. So the release is NOT shipped. Its measurement is recorded inM.reset. The holder afterguildsync_specis not found yet. - Harness pinned to
85c2946.
[v0.1.4] - 2026-09-29 - Who is attuned, crafting stations, and Guide me from the quest tracker
Retired quests leave the list, and a quest reads at the level you can take it (2026-09-28)
- Retired quests. The operator found "A Gently Shaken Gift" (8788) listed, with a reward the
game names "DEPRECATED ITEM".
QuestDBAdapter.LEFT_OUTnow drops QuestDB's newretiredhidden reason as well asunobtainable. QuestDB delivered the reason (thread fc73aa34, LibQuestDB MINOR 20). Its signals are Blizzard's "[PH]" placeholder text, and a reward the flavour's own client names DEPRECATED. That is 7 quests on Era (the mount-exchange placeholders, Black Ram Exchange among them) and 8 on Forever (those plus 8788). Per QuestDB's data, 8788's reward is deprecated only on Forever, so on Era it stays listed. - A quest below its required level reads at the required level. Black Ram Exchange is quest
level 1 with required level 60 in the data, so it sorted among level-1 quests (operator: "if it's
lvl 60 isn't it 60/60 not 1/60? ... it's making it confusing to navigate").
QuestData:EffectiveLevelraises a quest's level to its required level when the required level is higher, so it reads and sorts with the 60s.
Guide me from the game's quest tracker (2026-09-28)
- The operator asked to start and stop Questbook's pathing from the objective tracker, "alt+click
or some other combo".
Modules/TrackerMenu.luaadds Guide me (Questbook) or Stop guiding (Questbook) to the tracker's right-click menu, throughMenu.ModifyMenuonMENU_QUEST_OBJECTIVE_TRACKER. The quest is read from the block under the pointer. - KNOWN COST, and why not Alt+click: Blizzard's own click handler opens the quest map on every left
click it does not recognise, Alt included (Forever
Blizzard_QuestObjectiveTracker.lua:51-70). An addon can only add to that handler, so Alt+click would guide and open the map both. Stopping the map means replacing the handler, which taints the tracker. The right-click menu is Blizzard's documented extension point. Classic Era's quest watch builds no such menu, so nothing is added there. Not verified in game. - Tests: the harness keeps
Menu.ModifyMenuregistrations across a reset, and Questbook's test environment reloads the addon every reset. So each reset left a callback holding a whole copy of the addon and QuestDB's data, and the full suite slowed past 30 minutes until it looked hung.env_questbook.resetnow clearswow.menuModifiers, as the harness says to.
Reported to QuestDB (2026-09-28)
- The accept warning showed nothing on "Wolves Across the Border" (33), though accepting it closes the breadcrumb "Eagan Peltskinner" (5261) (player report, all other addons off). QuestDB's data stores 5261 as a pre-quest of 33 rather than a breadcrumb into it, so the warning has nothing to show. Asked of QuestDB on 68f14a22. QuestDB fixed this pair in its data. It measured about 37 more Era breadcrumbs stored the same way, with nothing in its source that separates them from real chain steps, so each is fixed as it is confirmed. Report further pairs to QuestDB with the quest ids. No Questbook change.
- Ironforge routes cut through buildings. Asked of QuestDB on 684f18d9. Their answer: the city walk links already carry the bots' routes, but were thinned to 8 yards, which cut corners through buildings. They now thin city walks at 2 yards, on all three flavours. No API change: the same polylines, with more points.
- On Questbook's side, from their advice to join a road at its nearest point rather than only at a
node:
Router:TrimToTargetcuts the last walk leg after its point nearest the target, when that point is nearer than the goal node. The route no longer runs past the target to a node beyond it and then doubles back in a straight line. The player's end already had the same rule at run time (Steer). Ironforge has no graph node in the Commons or at the Great Forge, so the straight line between the last road and such a target can still cross a wall. Not verified in game.
The Attunements tab explains itself, and shows who is attuned (2026-09-28)
- The operator asked for tooltips on the tab's rows, following the widget library's guidance. Each
step row's hover (RowList
onRowEnter/onRowLeave, drawn throughW:ShowTooltip/W:HideTooltip, withChrome.ShowTooltipon a library older than MINOR 34) says what its Status means, what the step needs ("Needs level 55 -- level 60 now", "Needs Honored with Argent Dawn", the quest's level and zone), who is on it, and what a click does. Every column header has aheaderTip, and the hovered row lights up (hoverHighlight). - The Guild column read as "done". In game a level-33 guildmate showed on "Reach level 55", and the operator read it as ticked (QuestDB's report d579e852). The header is now Guild next, its tooltip says it lists guildmates on the step they have to do next, and a guildmate on a level step carries their level: "Cinia (33)".
- A final Attuned row ends every attunement (operator, relayed on d579e852: "can we add a final
status on each of these, attuned. so we can see who is attuned?"). Its Status is Done when the
character on screen is attuned, and its Guild cell lists the attuned guildmates, sighted ones
marked "(seen inside)".
GuildOnnow returns the lists and the attuned names. - Clicking a Group or Guild cell shows everyone in the panel (the same message: "that list is
going to be HUGE and we need a way to see it all"). RowList
onCellClickcallsDetailPane:ShowLines, new, which shows a heading and one name per line with no quest in the pane. An empty cell's click falls through to the row. A crowded cell fits what it can and ends in a count ("Ann, Bob +3") throughcol.format's width and measure. - Verified in game by the operator, 2026-09-28: the tooltips, Guild next, the Attuned row, the panel lists and the In progress only filter ("validated it's all working").
- In progress only, on by default. A guild of hundreds would put hundreds of names on the first
two rows: everyone below 55 on the level step, and every level 60 who never started on the first
quest. The operator chose (2026-09-28): name only the guildmates who have started the attunement
(
AttunementView.Started: any of its quests done or in their log), plus anyone withinNEAR_LEVELS(5) of a level step, "a real list of people that officers can start paying attention to". Everyone else is a count in the cell ("142 below level 55", "38 not started"), and a click lists them in the panel under their own heading, highest level first. A strip checkbox, saved per character inchar.attunement.inProgress, turns it off to name everyone. The final Attuned row always names everyone.
Quests no database knows are listed, so they can be reported (2026-09-27)
- The operator found "Grund and Gozwin" in a WoW Forever quest log, with the pane saying nothing knows it, and asked for "some way to see/identify those, so we can give them to questdb".
Questbook:RecordMissingruns on every log record (Roster:Record). It keeps each quest the data source has no quest for in the account-wideglobal.missingQuests[<interface>][questId](name, level, first-seen date), so each client's list stays separate./questbook missingprints the ones in the current log with their ids, plus the account's total for this client. The detail pane's "Nothing here knows this quest" now shows the quest's ID and names the command.- QuestDB's thread ce6c4fba asked Questbook to fill such quests from the client's own quest APIs.
Built:
ActiveQuest:ClientWaypointreadsC_QuestLog.GetNextWaypoint(map id and 0..1 x, y, documented in Forever's QuestLogDocumentation.lua:251) andGetNextWaypointText. Both are feature detected and pcalled.ActiveQuest:SetandPointguide a held unknown quest to that waypoint, and re-read it on each log update. The pane lists the client's own objective counters and says when Guide me will follow the waypoint. Whether Forever's client returns a waypoint for its own quests is not verified; when it returns nothing, nothing is claimed. wow-version-replication.ps1: a Deleted event whose source file still exists becomes Changed, so a save-by-replace cannot remove a live file from the replica installs (TOGProfessionMaster peer review 1a2653fc). This copy polls, so the exposure was at most one poll. Not checked by any parser this session.
Crafting stations on the Places tab (2026-09-27)
- The operator asked for "a forge or anvil or other crafting stations to the places lists for all the areas that have them". QuestDB already carries them, so no new library is needed. They are spell-focus gameobjects (type 8) with names and spawn points.
QuestDBAdapter:GetTownsfolkgainsstations, chosen by name from type 8 (Adapter.STATIONS): Anvil (Anvil, Stone Anvil, Foundry Anvil), Forge (Forge, Heated Forge, Ogre Forge), Alchemy Lab and Mana Loom. The names come from all three data sets. Fires are left out because type 8 holds thousands of mob-camp campfires.QuestData.EmptyTownsfolkgains the key.PlacesIndex:StationRowslists one row per cluster of points withinSTATION_MERGE(150 yards). One template can stand in several cities, and a smithy has several anvils. Each row is named after the nearest graph place within 400 yards ("Anvil -- Kharanos") and tracked by world position. Points inside instances are left out. Four new kinds join the Places strip after Mailbox.
The Active tab no longer dies on a refused anchor (2026-09-27)
- The defect: on WoW Forever, opening the window on the Active tab raised "FilterStrip.lua:26: Action[SetPoint] failed because[SetPoint would result in anchor family connection]", which aborted the tab's draw. The client refuses an anchor that joins two anchor families when one is restricted or hangs off another root. Why the strip's pooled AceGUI frame was in another family here is not diagnosed.
- The change:
FilterStrip.AnchorBelowcatches the refusal and places the frame the strip's height below its parent's top instead. It also prints one chat line per session naming the error and what the strip hung from, so the next occurrence carries the evidence. This is a mitigation, not a root-cause fix.
The route walks into caves and buildings (2026-09-27)
- In game, a quest target inside the Frostmane cave in Coldridge Valley got a straight line over
the mountain. QuestDB's Forever graph has four nodes in the valley and nothing near or inside the
cave. Asked of QuestDB on thread 7ad98754; they delivered
GetSpawnApproach(kind, id, areaId, x, y)in LibQuestDB MINOR 19: a walk from a graph node to the spawn, shipped only where the straight line strays more than 25 yards. QuestView:TrackOfputs the spawn'sentityIdon the track when the location is the spawn itself.Tracker:TrackasksRouter:Approachfor the walk, which is feature detected and pcalled.Router:Routemakes the walk's first node the goal, never takes the straight line over it, and appends the walk as a leg. A player already nearer a point of the walk than any graph node joins it there. Specs inTests/router_spec.lua. Not verified in game; the operator's check is to track Grik'nir the Cold in Coldridge and follow the HUD.
No more ADDON_ACTION_BLOCKED on the HUD in combat (2026-09-27)
- The defect: in combat on WoW Forever, BugGrabber caught "AddOn 'Questbook' tried to call the
protected function 'QuestbookHud:Show()'", from
Hud:Renderon the OnUpdate tick. The target cue is a SecureActionButton parented to the HUD, and "Control restrictions on protected frames are also applied to their parents" (warcraft.wiki.gg, Secure Execution and Tainting). So every Show or Hide on the HUD is blocked during a fight, andRendercalledShowon every tick even when the HUD was already up. - The fix: every show and hide of the HUD frame goes through
Hud:SetVisible. It makes no call when the frame is already in the requested state. During combat it records a real change inpendingShown, and PLAYER_REGEN_ENABLED applies it. KNOWN COST: the HUD cannot appear or disappear mid-fight. A first track during a pull shows it when the fight ends, and a HUD that should hide stays up until then. Pinned inTests/hud_spec.lua. Not yet verified in game.
The route re-plans as soon as you walk off the spot it was planned from (2026-09-27)
- The defect: in Coldridge Valley on WoW Forever, the operator walked north and the HUD kept
pointing back at the route's first waypoint until a
/reload(the operator's words: "it told me to go backwards instead of adjusting to my new position", "stuck on that arbitrary initial pointer"). The first waypoint is the graph node nearest where the route was planned, and the only re-plan was the stray rule inTracker:Waypoint. That rule waits until the player is more than twice their starting distance from the waypoint, plusRouter.STRAY(100 yards). Walking away from a node 100 yards behind therefore kept the stale pointer for 300 yards. - The fix:
Tracker:RouteForrecords where each route was planned from (routeFrom, and the landing for a route re-planned in the air). While the cursor is still on the approach leg (the walk to the network), moving more thanRouter.REPLAN(25 yards) from that spot plans again from where the player is, which is what the/reloaddid. The stray rule is unchanged for the legs after the approach. Pinned inTests/router_spec.lua. Not yet verified in game.
[v0.1.3] - 2026-09-27 - The Attunements tab, guild attunement sync, and WoW Forever fixes
The addon list shows Questbook in the fleet's orange (2026-09-27)
- All three manifests'
## Titlenow read|cffFF8000Questbook|r, the fleet's brand colour, asked of every addon by DeltaSync (inbox dee4c21f) at the operator's request. Questbook is not a library, so no## Categoryline.
The guild sync's libraries install with Questbook (2026-09-27)
.pkgmeta'srequired-dependenciesgainsdeltasync,acecommqueueandlibguildroster(TOGBankClassic's slugs).Questbook.tochard-depends on all three, so without them a new Classic Era install from the CurseForge app would not load. KNOWN COST: relations are per project, so TBC and Forever players get them installed too, unused.
WoW Forever: the Active tab reads Forever's own quest log (2026-09-27)
- The defect: on WoW Forever the Active tab listed nothing, with quests in the log and no Lua
error (operator, in game, with Dwarven Outfitters in the tracker).
Modules/QuestLog.luaread the log only through the Vanilla bare globals (GetQuestLogTitleand friends). Forever's client carries the MainlineC_QuestLoginstead:GetNumQuestLogEntries,GetInfo(index),GetLogIndexForQuestID,IsComplete,GetQuestObjectives,GetQuestWatchTypeandAddQuestWatch, all documented in the Forever source tree. Which of Forever's bare globals misbehave is not verified; the fix does not depend on it. - The fix:
QuestLog.IsModern()is true whenC_QuestLog.GetInfoexists, and then the entries, the log index, the objective counters and the watch all go throughC_QuestLog. Hidden quests are left out. Era, TBC Classic and Anniversary document none of those calls in their own source trees, so they keep the bare-global path unchanged. - Tests:
active_specdrives a Forever-shapedC_QuestLogwith the bare globals taken away: headers and hidden quests skipped, complete read by id, index, objectives, name and the watch. QuestLog.lua is at 100% line coverage. - Still on the bare globals on Forever, not verified there: reading a quest's own text (the detail pane) and sharing with the party.
- Not verified in game.
WoW Forever: LibDBIcon embedded until it is released there (2026-09-27)
- Why: LibDBIcon-1.0 is not released for WoW Forever, and CurseForge does not offer it there, so Questbook's hard dependency on it would stop Questbook loading on Forever at all. The operator asked for the fleet's recipe (bank #account:25, FastGuildInvite v2.14.7) to be applied here.
- What changed:
Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua(MINOR 4) andLibs/LibDBIcon-1.0/LibDBIcon-1.0.lua(MINOR 56), copied unchanged from the standalone v12.0.3.Questbook_Camelot.tocalone loads them, first, before any Questbook code; drops LibDBIcon-1.0 from its Dependencies; and names it as its only OptionalDep, so a standalone copy loads first when a player has one and LibStub keeps the higher MINOR. LibStub and CallbackHandler come from Ace3. The Era and TBC manifests are unchanged and still hard-depend on the standalone. The files ship in every zip (.pkgmetamust not ignoreLibs); luacheck skips them. - Tests:
env_spec's manifest check names this as the Forever manifest's one difference (FOREVER_EMBEDS): the two files first and in order, the Dependencies and OptionalDeps lines, the files present in the repo, and no library in the Era or TBC manifests. - To undo once LibDBIcon ships for Forever: delete the two TOC lines and the
Libsfolder, put LibDBIcon-1.0 back onQuestbook_Camelot.toc's Dependencies line, drop its OptionalDeps line, and emptyFOREVER_EMBEDS. - Not verified: on a Forever client, and with an out-of-date standalone LibDBIcon also enabled.
Attuned by presence: guildmates seen inside a raid, Classic Era only (2026-09-27)
- What it does: a guild or sister-guild member seen inside Blackwing Lair, Onyxia's Lair or Naxxramas is recorded as attuned to it. It works for guildmates who do not run Questbook. The Character picker lists a guildmate known only this way with "(seen inside)", the steps show attuned for the raids they were seen in and unknown elsewhere, and the status line says when they were seen (today, or N days ago). A guildmate's own shared record wins over a sighting.
- Where it looks (
GuildSync:Observe, onGUILD_ROSTER_UPDATEandGROUP_ROSTER_UPDATE): the home guild's roster zone for each online member, through LibGuildRoster, and in a raid the raid roster's zone (GetRaidRosterInfo's seventh value). A sister guild's members carry no zone, so they are seen only in a shared raid.Attunements.SEEN_INSIDEnames the three raids by area id, andSeenRaidmatches the zone text against the client's own name for each. - How it travels: a sighting is kept in the observer's own store and carried in the observer's
own record (
EncodeSeen,Name-Realm=bits@timeper member), so it is stamped by the observer's canon and relayed like the rest of that record. Only the first sighting of a raid for a character changes anything, so a raid night does not republish the record. Members only: a guildmate who left drops out on the next save.Sightingsmerges every record's sightings and keeps the earliest, with who made it. - Peer Review fixes (inbox 7b47ad04), in the same code: a record whose completed quests are a
strict subset of the last one published is never published, since it can only be an early or
partial read (
GuildSync.Shrinks); a change undone within the 15-second delay sends no guild line; and a reputation event reads the record again only when a tracked standing moved. - Tests:
Tests/guildsync_spec.luacovers the observing, the wire text, the merge, the picker entry and its status line. - Not verified in game: any of it.
Guild attunement sync, Classic Era only (2026-09-27)
- What it does: every Questbook user's attunement progress is shared with their guild and sister guilds. The Attunements tab gains a Guild column (the guildmates whose next step is that row), a count of guildmates attuned in the status line, and guildmates in the Character picker marked "(guild)". Picking one shows their steps from what they shared, level and reputation included. The quest panel stays on your own character, because it needs a class and race the sync does not carry.
- The record (
Modules/Attunements.lua,TRACKED,EncodeBits,OwnContent,RecordWho): done and in-log bits over a fixed list of the sixty tracked quests (15 hex digits each), the level, one standing digit each for Argent Dawn and the Hydraxian Waterlords, and the side. The list is the wire format and is append-only; a spec fails if a step names a quest it lacks. It carries the STANDING, not the reputation total, so a kill does not republish the record. - The wire (
Modules/GuildSync.lua) is DeltaSync's numbered P2P, TOGBank's protocol, as the operator asked: one guild line with a 24-character entry per record held, and everything after it by whisper, paced by AceCommQueue. Members come from LibGuildRoster (this guild and its sister guilds); a record for anybody else is never advertised, served or taken. - The canon follows DeltaSync's README:
<publish time><content hash>, made in exactly one place (GuildSync:Save), for this character only, and only when the content changed. The publish time is read back out of the canon, so there is no second version marker. A receiver stores it verbatim and takes a record only when its publish time is newer.Saverefuses before the client answers the completed-quest set, so an empty login read cannot publish "nothing done". - When it runs: about 20-40 seconds after login, 15 seconds after the last change to the record, and on the new Sync guild button. The button and the login sync share one five-minute cooldown, saved per character, and the button counts it down on its face. Neither runs on a timer of ours: each falls due and the next client event sends it (the operator's rule that no timer touches the WoW API). The status bar shows FastGuildInvite's three states (checking, N/M records, complete) with its teal fill, only on this tab.
- KNOWN COST: the operator liked "only your own characters in the guild line", and the library cannot do that as well. Its broadcast lists every record the sender can serve, and a peer reads a record left off the list as one the sender lacks and offers it back. So the line is 24 characters per character held, members only: about 1.2 KB for fifty Questbook characters.
- A library gap, stood in for: DeltaSync parks a bare offer whose numbers it cannot resolve
yet, but not a broadcast. On a guild's first sync the listener adopted the broadcaster's number
table and learned nothing else until somebody broadcast again.
GuildSync:NoteBroadcast/ReplayBroadcastskeep such a line and read it again on the next client event after the table arrives. Asked of DeltaSync (inbox 12ca628b), which deliveredParkBroadcastthe same day: the stand-in feature-detects it and stands aside, so a line is never replayed twice. Its two specs hide the method on the instance to reach the fallback, and a third pins that it stands aside. Delete the stand-in once every supported DeltaSync carries the method. - Dependencies, Classic Era only:
Questbook.tocnow requires DeltaSync, AceCommQueue-1.0 and GuildRoster. The TBC and Forever manifests are unchanged; env_spec names the three as the Era-only tail of the Dependencies line, as it already named the Attunements files. - Tests:
Tests/guildsync_spec.lua, 33 examples on the real DeltaSync, LibGuildRoster and AceCommQueue, two of them with two players on the harness's wire, each with their own copy of every library: each record reaches the other with its canon untouched, and an offline member's record is relayed by whoever holds it. The README's own canon test is there: a planted canon survives every read, advertise and serve path. GuildSync, Attunements and AttunementView are at 100% line coverage. The test env now loads the harness's guild module (IsInGuildand the roster), and loads the sync's libraries only for specs that ask: DeltaSync's four largest files alone are 251 KB. - A future-dated record is refused: ordering is by the publish time inside the canon, so a
record dated years ahead (a broken clock, or a hand-made one) would have beaten every real one
on every client, for good.
GuildSync:Acceptnow refuses a publish time more than five minutes past the server clock (MAX_FUTURE). Found in this session's self-audit. - Not verified in game: any of it. It needs two Questbook users in one guild.
Harness adoption and twenty weak assertions (2026-09-27)
- Harness pinned at
50589c6(from7e9c2fe). Questbook'sIsUnitOnQueststand-in is gone, replaced by the harness's own. So is the tooltip stand-in, once the harness modelled theOnTooltipSetUnithandler Era's GameTooltip declares (asked in inbox ed87f83e, delivered as2c4868e). - Twenty
is_nilchecks on a literal key (writ's new spec check, inbox d2227ad1): a misspelled key passes such a check for ever. Each is now paired with a positive check on the same key, or reads a key derived from the code's own tables. One real weakness surfaced:questdb_provider_spec's "does not cache an empty townsfolk table" ran its second half after a full reset, so it never looked at the first adapter's cache. Its comment now says so; the first half is what guards the regression.
Redraws that could judge a quest or a level one event too early (2026-09-27, Peer Review 516c381d)
- A turn-in: whether the client's completed set already holds the quest at
QUEST_TURNED_INis not verified. Both the Attunements tab and Core read it at that moment, so a step or a Browse status could still say not done after the hand-in. Both now also redraw on the nextQUEST_LOG_UPDATE, one time only (a flag cleared on use), which is when Roster already re-reads the set. The Attunements tab's view keeps its own flag. Core's makes the standing change again. - A level-up: inside
PLAYER_LEVEL_UP,UnitLevel("player")is said to still answer the old level (warcraft.wiki.gg; not verified on this client). So a "Reach level 55" step, and a scaling quest's level on the immediate Browse redraw, could be judged at the old level. Core and the Attunements tab now record the event's level inPlayer.dinged, andPlayer.Levelnever answers below it. Both handlers record it, because the order the client runs them in is not ours to know. - The Naxxramas tier label: the "(Honored, Revered, Exalted)" label was hard-coded in
Attunements:StepNamefor every anyOf step. The tiers now live in the step data (standings), and a spec fails on an anyOf step without them. - The reputation threshold comment now cites Blizzard's Era reputation frame and the wiki instead of Availability's note. Peer Review traced it and the thresholds were right.
- My own audit note was wrong about Roster. I said it shared the turn-in timing assumption. It does not: it re-reads the set on the next log update, which is the fix above.
- Tests:
Tests/timing_spec.lua(4 examples) and three inattunements_spec. Player, Core, Attunements and AttunementView are all at 100% line coverage.
Raid members outside your group, through other Questbook users (2026-09-27)
- The gap: the client answers "is this person on this quest?" (
IsUnitOnQuest) only for party1-4, which in a raid is your own subgroup. A raid member with no addon outside your group showed nothing. - Source 4 in
Modules/Party.lua, the relay. In a raid, every Questbook user asks the client about their own subgroup, for the quests in their own log (SubgroupSightings), and sends the answers with their log ass. With one Questbook user per group the whole raid is covered. The operator: "i like this, lets add it to the feature list". A member who sends their own Questbook log is left out, because their own report is better. An empty list is sent too, so a dropped quest is cleared on the other side. - Received reports are kept per member, per reporter (
Party.relayed). A new report replaces only that reporter's last one, two reporters' quests add up, a message with no report clears the sender's, and a report about yourself is ignored. Both the member and the reporter are dropped when they leave the group. - Re-sent on
UNIT_QUEST_LOG_CHANGEDfor a party unit (Core), the event Era's own quest log listens to for its party marks (Blizzard_UIPanels_Game/Vanilla/QuestLogFrame.lua:29). The fingerprint now covers the report too, so nothing is sent when it has not changed. - Where it shows: the quest panel's Party section adds "(seen by Eve)"; the mob tooltip says
"has it", as for the client's own answer; the Active tab's picker lists the member's relayed
quests with their own status line; and the Attunements tab has a new Group column naming
who in the group or raid is on each step, from every source. The column is live, so it is left
empty when viewing an alt, and it redraws on party news. Attunements wraps
Party:Changedfor that, so the sharedParty.luanames no Classic-only tab. - Limits, as stated to the operator: only quests the reporter also holds, and nothing about completed quests. It shows "who is on this step now", not "who is attuned". It needs a Questbook user in the member's subgroup.
- Tests:
Tests/raidrelay_spec.lua(15 examples) and one Group-column example inattunements_spec. Party, DetailPane, UnitTooltip, ActiveView, Attunements and AttunementView are all at 100% line coverage. The event test counts calls on this instance's Party rather than messages sent, because the harness keeps earlier examples' event registrations and each of them sends. - Not yet verified in game. It needs a raid with a second Questbook user in another group.
The Browse tab is ready before it is opened (2026-09-27)
- Reported by the operator: "the first time i open tabs, there is a 100-300ms delay dependant on the tab". Measured offline against QuestDB's real Era rows (4,160 quests): Browse's list takes 49 ms to build and its status pass 261 ms. That pass was 994 ms in game on 2026-09-23. It is the delay that shows. The operator: "i'm ok with just warming the browse tab, as you're right, that's the one that causes the most noticable 'pause'".
Modules/Warmup.lua, new, in all three manifests: after QuestDB reports ready, a hidden frame builds the Browse list and then fills every quest's status cache, about 4 ms per frame (BUDGET_MS, againstdebugprofilestop; one step of 25 quests per frame without it). It waits 2 seconds after its trigger (DELAY), does nothing in combat, and hides its frame when done, so an idle warm-up costs nothing per frame. Opening Browse before it finishes builds the rest on the spot, the same as before.- Warmed again, not just at login: Core starts it after anything that throws the caches away: a quest taken, dropped or handed in, a level, a reputation or skill change, and QuestDB switching source. Before this, the next Browse open after any of those paid the whole status pass again.
- Not warmed: Rewards, Places and the mob/item name search stay built on first use. Places measured 13.0 MB offline for 1,812 places, which looks like QuestDB decode caches rather than Questbook's rows. That is not established, and warming it would hold that memory in every session, so it was left alone.
Index:Buildsplit intoNewBuild/BuildStepso the list can be built in slices.Index.generation(bumped byInvalidate) lets a sliced build tell it went stale. A finished sliced build is installed only into an empty index, so it never swaps the table under a list that built its own.Build()still rebuilds on request.QuestStatus.generation(bumped byRefresh) lets the warm-up restart its status pass when the caches are dropped under it.- Tests:
Tests/warmup_spec.lua(11 examples): the delay, slicing, combat, re-warming after a quest is taken, restarts mid-pass and mid-build, and the no-clock path. Warmup, Index and QuestStatus at 100% line coverage. - A defect I made and caught before it shipped:
QuestStatus.generationwas first nil until the firstRefresh, so the warm-up's "same generation" test compared nil with nil and never set its position; every status step then raised. My first spec loopedwhileon that position with no cap, and the whole suite hung until the 15-minute limit. Now the field starts at 0, the warm-up checks for a missing position explicitly, and every loop in the spec is capped. - Verified in game (operator, 2026-09-27): "i waited about 10 seconds after reload, opened qeustbook and the browse tab loaded immediately." Not checked: opening Browse within the first couple of seconds, while the warm-up is still running, and in combat.
The Attunements tab, Classic Era only (2026-09-27)
- What it is: a fifth tab for the raid and dungeon attunements: Molten Core, Onyxia's Lair
(Horde and Alliance chains), Blackwing Lair, Naxxramas, the Shadowforge Key, the Skeleton Key (both
sides) and Aqual Quintessence. Pick one to see its steps: the level, the reputation and every quest in
order. Each step shows Done, In log, Next, or
?where Questbook cannot tell. Click a step to open its quest in the panel on the right, where Guide me works as usual. The dropdown shows each attunement's progress, and the status line says what is next and how many attunements are done. - Handed over from Dibs (DIBSREQ-QB-002, inbox f9984ab1). The operator, 2026-09-27: "i'm thinking attunement belongs in Questbook, not in Dibs". Building it: "we'll have it in classic only first ... so we need to be surgical about applying it only in the classic .toc", "ensure you're using the widgets library" and "follow the style/feel of the rest of the tabs".
- Classic Era only, by manifest.
Modules/Attunements.lua(the steps and the rules) andModules/AttunementView.lua(the tab) are listed at the end ofQuestbook.tocand in neither sibling. The view adds its own entry toWindow.TABS/Window.VIEWSand registers its own events frame, soWindow.luaandCore.luaare unchanged and TBC and Forever show the same four tabs as before.env_specnow allows exactly those two files as an Era-only tail. The shared list above them must still match the siblings file for file. - Built like Active: the shared strip (LibAceGUIWidgets' LAGW-Strip), a LibAceGUIWidgets RowList on the left and the quest panel on the right. The Character picker lists the same recorded characters as Browse and is hidden when there is only one.
- Our own data, not Attune's. The installed Attune addon was only a guide to which attunements
to track: it has no licence file, so its data is not ours to copy. Every step is a quest, a level or
a reputation, and all 60 quest ids were checked against QuestDB's Classic Era rows: names, sides and
chain links.
attunements_specre-checks every id against the shipped rows and fails on a missing id or one from the wrong side. Kill, loot and talk steps are left to the quest panel, which already shows them. Keeping to quests also makes every step answerable for an alt. - For an alt: quests come from the roster's recorded completed set and log. Level and reputation
are not recorded, so those steps show
?rather than borrowing this character's figures. - Not in this version: the Scepter of the Shifting Sands chain (long and branching; left for its own pass), guild-wide sharing of who is attuned, and TBC.
- Tests:
Tests/attunements_spec.lua(24 examples, against QuestDB's real Vanilla data); both new files at 100% line coverage. - Five window-size specs fixed. LibAceGUIWidgets'
PersistWindownow caps a window to the screen (ClampWindow, its MINOR 36). The harness screen is 1024 wide, narrower than Questbook's 1060 default, socore_specandwindow_specwere reading the cap's answer.env.wideScreen()gives those five examples a 1920x1080 screen. The window code itself did not change. - Verified in game on Classic Era (operator, 2026-09-27): "it's picking up my attunements shows the steps, as it is, attunement is working well". Not checked: that TBC still shows only the four shared tabs, and the alt picker.
[v0.1.2] - 2026-09-26 - Your party's quests, your alts' quests, and one quest database
- Verified in game (operator, 2026-09-26): "all tabs right, alt stuff works" -- the shared filter strips on Browse, Active, Rewards and Places, and browsing the quest list as an alt.
Guide me walked Alliance players past flight paths they knew (2026-09-25)
- Reported in game: an Alliance player at Darkshire, who knew the Stormwind flight path, was
walked across Elwynn to the Stormwind library. I wrote the name matching in
FlightPaths:KnowsandRouter:FlightMasterNamedas "the client's taxi name begins with the graph's name", and 13 Era flight masters fail that: Stormwind ("Stormwind City" vs "Stormwind, Elwynn"), Ironforge (graph "Dun Morogh"), Lakeshire ("Redridge Mountains"), Undercity ("Ruins of Lordaeron"), Theramore, Feathermoon, the Crossroads ("The Crossroads" vs "Crossroads, The Barrens"), and every name with an apostrophe (Grom'gol, Rut'theran, Nijel's Point, Marshal's Refuge, Zoram'gar, Morgan's Vigil, Light's Hope). Once a continent's flight map had been read, an unmatched flight master counted as undiscovered, so the route refused the flight. The same failure also stopped a taken flight's landing being recognised, so the route was not re-planned from where the player landed. - Fix:
Router.TaxiNameMatch, the one rule both callers now use. It ignores apostrophes, maps the graph's name throughRouter.TAXI_ALIASESwhere the client calls the place something else, and requires the match to end at a comma or at the end of the name. The aliases were read off the client's own TaxiNodes table (wago.tools, classic_era and anniversary, 2026-09-25), which also covers the TBC and Outland names (Shattrath, Silvermoon, Zul'Aman, the Dark Portal, Telredor...). "Zangarmarsh" means Telredor by position: the TBC graph's only Zangarmarsh flight master (node 2811) stands about 80 yards from its "Zangarmarsh Telredor" place (node 2804). Peer Review found the rows. - The comma boundary is a second fix. "Southshore Ferry, Hillsbrad" is a client taxi row as well as "Southshore, Hillsbrad", and the longest-name rule let the ferry answer for Southshore. Whether Era's flight map ever lists the ferry row is not verified. On Anniversary the same node 46 is "Transport, Southshore" (WoWAPITesting, from the wago.tools tables), so the clash is Era-only.
Tests/router_spec.luanow reads every flight master out of QuestDB's Vanilla, TBC and ForeverPaths.luaand requires each to match a real client name, so a new graph name without an alias goes red. The client names come from the harness's per-flavour TaxiNodes rows (env/taxinodes.lua, WoWAPITesting7e9c2fe, inbox cba6c6d9), which replaced the list I had typed into the spec. A second spec pins the Telredor alias to the node positions. The old flight-path spec was reworked because it asserted the longest-name behaviour.- Not covered: Forever's own flight-map names. The harness has no Forever rows, so Forever's graph is only checked against the Era and TBC names. A pending spec says so.
- Harness pin moved to
7e9c2fe. The other entries it brings in ask nothing of Questbook. - Not yet verified in game.
Questie is no longer a data source (2026-09-25)
- QuestDB is the only place Questbook reads quest data from.
Modules/QuestieAdapter.luais deleted, and so is the/questbook sourceswitch that chose between the two;/questbook sourcenow just opens the window like any unknown word. Questie is gone from the## OptionalDepsof all three TOCs and from.pkgmeta. The operator's call, 2026-09-25, after the gap analysis showed Questbook read nothing from Questie with QuestDB selected. - The auto-accept hold stays. The operator: "leave that auto accept integration in that is
usefull for breadcrumbs we're hoping to surface". It moved into
AcceptWarning:HoldQuestieAutoAcceptand still stops Questie's auto-accept from taking a quest the accept warning has something to say about. With Questie no longer a dependency, nothing orders the two addons' loading, so Core calls it at initialise and again atPLAYER_LOGIN. Peer Review confirmed the hook lands: Questie creates itsAutoQuestingmodule when the file loads (Questie/Modules/Auto/AutoQuesting.lua:5) and itsQUEST_DETAILhandler calls it through the module table (EventHandler.lua:153). Not verified in game. - Asked QuestDB (inbox 6786d5df) whether its
QuestieSource.luastill finds Questie's curated rows now that Questie keeps its database in a separateQuestieDBaddon. - Tests: the specs used a fake Questie. They now load their small quest worlds into the real
LibQuestDB through
Tests/fixture_questdb.lua. The specs that read Questie's real data are deleted, along with the fake and the Questie/QuestDB shape-parity spec.
Fixed on the way
- An NPC with no known level read "Level 0 Elite" in the detail pane. QuestDB stores a missing level as 0, and the adapter now reads 0 as unknown, so the pane says "Elite".
- The Ready rule gave a hunter the wrong reason for a shaman quest. It said the quest was waiting on a pre-quest; it now says "Class requirement", because no pre-quest will ever make it a hunter's quest. This checks the class before the pre-quest, where Questie checks it after. The verdict is the same either way; only the reason changed.
- Removed dead code.
QuestData:MissingPrerequisiteshandled negative pre-quest entries, and a comment said QuestDB passed Questie's signed lists through. It does not: QuestDB'sQuestieSource.luaturns them into plain ids first.Player.ClassTokenand the detail pane's Questie-only objective text had no caller left and are gone too.
The party's quests, and who needs a mob (2026-09-26)
- Asked for by the operator: "can i see all the quests for everyone in the party, whether they
have the addon or not?", then "can we do the thing that questie does with the mouse over a mob to
show who needs it for a quest?". The client cannot read another player's quest log, so
Modules/Party.luacombines three sources, best first:- Questbook users send their whole log, each objective's counter included, over AceComm
(prefix
QuestbookParty, AceSerializer). It is sent on the log update only when it changed (a fingerprint compare, no timer), and swapped with everybody when the group changes. - Questie users, when Questie is installed here: Questie's already-decoded
QuestieComms.remoteQuestLogsis read, never written and never decoded from Questie's wire format, which is its own serializer and is changing. - Everybody else:
IsUnitOnQuest(logIndex, unit), as Blizzard's own log uses it (Vanilla/QuestLogFrame.lua:557). It works only for quests in your own log, and gives no progress.
- Questbook users send their whole log, each objective's counter included, over AceComm
(prefix
- Every name is normalised to
Name-Realmbefore matching, so a party member from a connected realm is matched the same as one from your own. - Where it shows: the Active tab's Character picker lists party members ("Bob (party)"), showing
their log read-only with a status line that says how complete the picture is. The quest pane gets
a Party section with each member's progress. A creature's tooltip lists every quest (yours and
the party's) it counts for and who still needs it, leaving out whoever is done
(
Modules/UnitTooltip.lua, theOnTooltipSetUnithook and GUID parse Blizzard's own Era code uses, Blizzard_PTRFeedback_Tooltips.lua:86-91). Which mob counts comes from QuestDB's raw rows: kill targets, shared kill credits, and every mob that drops an objective item. A General setting turns the tooltip off. - Self-audit and Peer Review (thread 2a9cf5b4) fixes, same day, each following what Questie does:
- Non-English clients listed finished players as still needing a mob. A counter was matched by
its English item name. It is now matched by the client's own item name (
C_Item.GetItemInfo), then the English name, then by its position among counters of the same kind -- Questie's own mapping (QuestieQuest.lua:1376-1394, log index toObjectiveData[index]). Position is used only when the client lists as many counters of that kind as QuestDB does; otherwise the member is shown without a count rather than with a wrong one. Party snapshots now carry each counter's kind (k) so the same match works for remote logs. Peer Review's second pass found two gaps, both fixed: a quest with a kill-credit group never matches a monster by position (the plain kills and groups are in Questbook's order, not QuestDB's, so equal counts could swap two counters), and a name match is refused against a counter of QuestDB's other kind ("Gray Wolf" no longer takes the "Gray Wolf Pelt" counter). The client's other counter types ("object", "event") still match by name. - The mob tooltip reads the item name through
C_Item.GetItemInfoonly; the bareGetItemInfofallback is gone, as it is a deprecation fallback and every shipped flavour has the namespaced one (Blizzard_FrameXMLUtil/ItemUtil.lua:225 calls it in the Era, Classic, Anniversary and Forever trees). - A mob serving several objectives counted for only the first. A kill target that also drops two quest items is now three needs. A kill-credit group's counter is matched by the group's base NPC.
- The tooltip hook could error at startup on WoW Forever. It now takes Questie's route order
(Tooltip.lua:525-553):
TooltipDataProcessorwhenGameTooltip.GetPrimaryTooltipDataexists (Forever), otherwiseHookScript("OnTooltipSetUnit")only afterHasScriptconfirms it. Forbidden tooltips are skipped, and nothing is added inside instances on Forever, as Questie's TooltipHandler.lua:13 does. - Raids:
IsUnitOnQuestis asked only through the member'sparty1-party4token, the only way Blizzard's code calls it. - A party update arriving on another tab left the quest pane stale. It now redraws in place on any tab.
- Each hover re-read the quest log once per quest. The log is now read once per hover into a
quest-id map (
UnitTooltip.Logged) and handed toWhoandHeldQuests. The snapshot cost on every log update (finding 4) is left until measured, as Peer Review agreed. - Departed party members were reported as a leak; checked and not one -- the character picker and the tooltip read only the current group.
- Non-English clients listed finished players as still needing a mob. A counter was matched by
its English item name. It is now matched by the client's own item name (
- Specs:
party_specandunittooltip_spec. The harness models neitherIsUnitOnQuestnor the tooltip'sOnTooltipSetUnit, soTests/env_questbook.luastands inIsUnitOnQuestand sets a stand-inOnTooltipSetUnithandler onGameTooltip. Both are requested from WoWAPITesting. Full suite green in normal, reverse and shuffled order (854 passed, 0 failed, 1 pending); coverage 7256/7256 lines before the second-pass fixes,UnitTooltip.lua170/170 after them. - Not verified in game: any of it. In particular, whether AceComm's party channel reaches a
connected-realm member, how Questie keys a same-realm sender in
remoteQuestLogs(read as the bare name AceComm hands it, then normalised), the ForeverTooltipDataProcessorroute, position matching on a non-English client, andIsUnitOnQuestwith party tokens inside a raid.
Self-audit fixes, ruled by Peer Review (a97ea5e1, 2026-09-26)
- A reward click kept the alt. Window:ShowQuest cleared every filter to show a hidden quest, and Clear now also reset the Character dropdown, so viewing an alt and clicking a reward silently switched Browse back to You. The character and its side survive the clear.
- An alt's completed set is read on events, not on every loot. Roster:RecordFacts walked the whole completed set on each QUEST_LOG_UPDATE. It now reads it once per login (the first update whose answer is not the login-time empty set) and again after each QUEST_TURNED_IN (Core's new OnQuestTurnedIn -> Roster:CompletedChanged). No timer polls GetQuestsCompleted.
levelis no longer recorded: nothing read it. Existing records drop it on the next login.- Rewards searches with the shared rule (SearchBox.Match) instead of its own copy; a parity spec runs one table of queries through Browse's Index.Matches and SearchBox.Match and asserts they agree.
- The fallback FitCheckBox reads the box width as the library's does (checkbg), not a literal 24.
- The lists hang from their strip's own bottom edge (FilterStrip.AnchorBelow) on Browse, Active and Rewards. FilterStrip.HEIGHT, ActiveView.STRIP_HEIGHT/LIST_GAP, RewardsView.HEIGHT/LIST_GAP and Window.LIST_GAP are gone; FilterStrip.LIST_GAP is the one gap. Removes 16px (Browse) and 34px (Rewards) of dead space.
- The window floor follows the measured row. Window:FitFloorToStrip raises the floor, and widens the window, when the Browse strip the client actually drew is wider than SIZE.minWidth (For me is as wide as the client's font makes it). Never lowered.
- The Places tab joined them (same day, the item Peer Review left open): its strip is on the shared strip layout with the shared search field (placeholder "Place or zone"), its list hangs from the strip's bottom, PlacesView.HEIGHT and LIST_GAP are gone, and PlacesIndex's search is SearchBox.Match. All four tabs now build their strip the same way.
The filter strips line up (2026-09-26)
- Reported by the operator, from screenshots: "the spacing here is still odd, we should make it uniform, the same with the vertical alignment". AceGUI's Flow layout could not be made uniform: a labelled EditBox is a 44px frame with its box 25px down, a labelled Dropdown a 40px frame with its box 14px down, Flow aligns them by different offsets, and buttons, checkboxes and the search box are 24px with no label. So no two kinds shared a caption line or a box line.
- Browse, Active and Rewards now use one strip layout (
FilterStrip.Layout, registered asQuestbookStrip): controls go in unlabelled, each caption is a separate gold Label on one line at the top, every control is centred on one line under it, and every pair of controls has the same 8px gap. A row that runs out of width wraps; a hidden control (the Active tab's picker with one character) takes no space. "For me" and "Usable by me" are sized to their words, which removes the hole after them. - The Browse row with its gaps needs 928px, so the window's floor rose to 1036 and its default width to 1060. A saved size under the floor is raised on open, as before.
- Asked of LibAceGUIWidgets (inbox thread c2be4737): the same layout in the library, so every addon's
strips line up without hand-tuning (FastGuildInvite positions its strips by hand today). Delivered the
same hour as
LAGW-Strip(MINOR 35:W:NewStrip,W:StripAdd,W:StripRowWidth,W:FitCheckBox), whose gaps and caption line also follow the UI scale. Questbook's strips now go through it whereverFilterStrip.Library()finds it;FilterStrip.Layoutstays as the fallback for an older library copy. The layout specs run twice, once on each. - Specs in
filterstrip_spec("the strip layout"): equal gaps, one centre line, captions on one top line over their controls, a hidden control taking no space, a 300px strip wrapping by exactly one row, and the whole Browse row fitting the floor unwrapped. Suite 887 passed, 0 failed, 1 pending; FilterStrip, ActiveView, RewardsView, SearchBox and Window 1210/1210 lines.
One search field on every tab, and searching by zone (2026-09-26)
- Asked for by the operator: "add ability to filter quests so they can show only quests in a specific zone, etc. make it the text entry search one from widgets library. add it to the browse and active tabs. update the search on the rewards to use the widgets search bar/layout/text."
Modules/SearchBox.lua: LibAceGUIWidgets'CreateSearchBox(Blizzard's SearchBoxTemplate -- magnifier, greyed placeholder, clear-X) wrapped as an AceGUI widget type,QuestbookSearchBox, so it sits in a Flow strip like any control. The wrapper is TOGBankClassic'sTOGBankSearchBox; the matching rule is the library'sSearchMatch(every word somewhere in the fields), with the same rule inSearchBox.Matchfor a library copy that lacks it.- The library now ships the wrapper itself as
LAGW-SearchBox(LibAceGUIWidgets MINOR 35, asked for on inbox thread 293177a6 and delivered the same morning).SearchBox.Createuses it whereverAceGUI:GetWidgetVersion("LAGW-SearchBox")answers, and falls back toQuestbookSearchBoxon an older library copy, which a player can have while running this Questbook. - Browse: the labelled Search box is the new field, placeholder "Quest, zone, mob, item or ID" (later shortened to "Quest, zone or ID", which fits the box on one line; the hover tooltip still says mobs and items match). Its matching is unchanged -- it already took every word against the name, zone and id, plus the mob/object/item name index -- so typing a zone shows only that zone's quests.
- Active: a new search field in the top row (220px, later 200px as on Rewards), matching each quest's name, zone, level and id. The status line keeps describing the whole log and adds "(N shown)" when the search narrows it. The text survives closing the window, as the chosen character does, and applies to whichever log is on screen.
- Rewards: the labelled Search box is the new field, placeholder "Item, type or quest"; its matching is unchanged.
- Spec:
searchbox_spec(the widget's contract, the rule with and without the library's matcher, and the three tabs). Suite 882 passed, 0 failed, 1 pending, in normal, reverse and shuffled order; coverage of SearchBox, ActiveView, FilterStrip and RewardsView 880/880. Not verified in game -- in particular how the 24px box lines up beside the labelled dropdowns in each row.
Browse the quests for an alt (2026-09-26)
- Asked for by the operator: "add alt dropdown to the first tab so you can search the quests for your alts". The Browse tab's filter strip has a Character dropdown: You, then every other character Questbook has recorded. Choosing one judges the list for that character -- "For me" by its class and race, the Status column by its completed quests and its log, and the quest pane's header and standing line -- and puts Side on its side. Clear goes back to You.
Modules/Roster.luanow writes down who each character is whenever it records the log (RecordFacts): class and race ids, side, level, and the completed set as a sorted id list. The completed list is never overwritten by an empty answer (the login window again), and is left alone when its count has not moved, since the set only grows and this runs on every loot.Roster:Subjectturns a record into what the rule reads, memoised per session so its answers stay cached.- The alt is passed through, never swapped in:
Availability:Of,QuestStatus:Of/Reason/LabelFor/ Sentence/Standing,QuestData:ForPlayerandIndex:Filtertake an optional subject, and the pane holds one only while Browse is showing. So the mob tooltip, the HUD, the accept warning and the Active tab keep answering for the character that is logged in. - What an alt cannot be judged on: reputation and profession skill are read live from the client and are not recorded, so a quest gated on either shows no status for an alt rather than one borrowed from the character logged in. A scaling quest's level is still this character's. A character last played before this change is not offered until it is logged in once more.
- The window's floor and default width grow by the dropdown's 120px (870 to 990, 900 to 1020), and again with the strip layout's gaps to 1036 and 1060 (see "The filter strips line up"). A saved size under the new floor is raised to it on open, as before.
- Spec:
altbrowse_specover QuestDB's real Vanilla rows (a Human Mage, a Troll Shaman, a Troll Warrior and a record from before this change). Suite 872 passed, 0 failed, 1 pending at the time; coverage of every changed module 1970/1970. Not verified in game.
"Always show the HUD" (2026-09-25)
- Asked for by the operator in game: "why do we hide the hud when a target isn't there? it's
kinda useful, make an option to always show the hud". A new Appearance setting, Always show the
HUD, off by default. On, the HUD stays up with nothing tracked as a compass: the heading tape on
the player's facing, "NO TARGET", and the hint for picking a target (
Hud:RenderIdle). Stopping a track goes back to the compass rather than to nothing. With no facing (inside an instance) the words stay and the tape hides. "Show the HUD" off still hides it. Tracker:Samplenow carries the player's heading in its no-target state, which is what the compass draws from.- Fixed on the way: a message the HUD put up with nothing tracked -- "NO PLACE" for an active
quest the database has no location for -- was hidden again by the next once-a-second redraw. It
now stays until the next track or stop (
Hud.message). - Specs:
hud_spec"keeps the HUD up as a compass..." and "keeps a message on screen across ticks...". Not verified in game.
LibDBIcon-1.0 is a required dependency, not an embedded copy (2026-09-25)
- Asked for by TOGTools (inbox 69a8c6a7), on the operator's decision: an embedded LibDBIcon only updates when the addon that ships it is re-released; the standalone addon updates for every player.
Questbook.tocandQuestbook_TBC.tocaddLibDBIcon-1.0to## Dependenciesand no longer loadlibs\LibDataBroker-1.1.luaorlibs\LibDBIcon-1.0.lua. The standalone brings LibDataBroker-1.1 with it (itsembeds.xml)..pkgmetarequires the CurseForge sluglibdbicon-1-0.- WoW Forever too, and the embedded copy is deleted. I first kept
libs\forQuestbook_Camelot.toc, because the Era build of the standalone does not list interface 16001 (LibDBIcon-1.0.toc:1, v12.0.3). The operator then ruled "make it an external hard dep", so all three TOCs require LibDBIcon-1.0 and thelibs\folder (LibDataBroker-1.1 and LibDBIcon-1.0) is gone. No Lua changed. Tests/env_spec.luaagain requires all three manifests to load the same files and dependencies, and asserts no manifest loads anything fromlibs/and that the embedded copy is gone. The test env loads the installed standalone through the harness (libs.fresh("LibDBIcon-1.0")), and the minimap specs run against it.- Known cost: a player who installs by hand must now install LibDBIcon-1.0 as well. On Forever, whether Forever's own build of LibDBIcon-1.0 lists 16001 is not verified; if it does not, it loads only with "load out of date AddOns", as Ace3 already does there.
- Verified in game (operator, 2026-09-25): the minimap button works after a
/reloadwith the standalone installed, so the load-on-demand library is loaded as a dependency.
[v0.1.1] - 2026-09-24 - Other addons can guide with the HUD; quest text in your own language
Other addons can guide the player with Questbook's HUD (2026-09-24)
Questbook:TrackPlace(target), for ItemDB's new Where to get it window (contract LIBREQ-QB-TRACKPLACE). Clicking a mob, vendor or node row there starts the HUD on it, exactly as a Places row click does, and switches a hidden HUD back on because the click is deliberate.targetis the tracker's world-yards shape,{ name, kind, world = { continent, x, y }, location = { zoneName } }. Only those fields are copied, so the caller's table is never held. It answers a clean false and never raises when the target has no numericworld.continent/x/y, or when the addon is not initialised.Questbook:StopTracking()andQuestbook:IsTracking(), for the stop icon on the same window (contract LIBREQ-QB-STOPTRACKING).StopTrackingis the universal stop that the window's own stop icon and/questbook hud offalready use, so it stops whatever is being guided, not only a place ItemDB sent. Both answer false before the addon is initialised.- As with
ShowQuest, a method's presence is the feature flag, and there is no version to check. Pinned byTests/trackplace_spec.lua. Confirmed working in game by the operator, 2026-09-24.
A quest in your log shows its description in your own language (2026-09-24)
- For a quest the character is carrying, the detail pane's Description and Objective text now come from the game client, which gives the server's own words in the player's language. QuestDB's text is English only, so on a German or French client every description used to be English with a "shown in English" label. Quests not in the log keep QuestDB's text, as before.
QuestLog:Text(Modules/QuestLog.lua) reads it the way Blizzard's own log does:GetQuestLogQuestText()answers only for the SELECTED log entry (Vanilla/QuestLogFrame.lua:355-367), so it saves the player's selection, selects the quest, reads, and restores the selection. That is the pattern Blizzard's own WatchFrame_AbandonQuest uses (Wrath/WatchFrame.lua:1184-1197).- Not verified: whether any of these calls is restricted. No restriction flag was found either
way, so the read is
pcalled and the selection is put back even when the read raises. Each quest is read once per session, because every read briefly moves the log selection. - Pinned by nine examples in
Tests/questtext_spec.lua. Confirmed working in game by the operator, 2026-09-24; not yet checked on a non-English client. - Harness pin moved to
2d4b78f, which modelsGetQuestLogQuestText()on the quest log (Questbook's request 63f4ea90). The stand-inTests/env_questbook.luacarried for it for an hour is deleted, and the read-once example spies on the harness's call instead of reading the stand-in's counter. 862 green on the new pin.
Older versions (v0.1.0 and earlier) are in CHANGELOG_ARCHIVE.md, moved whole so this file stays
under the 125,000 characters GitHub accepts as a release body.
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include