Questbook

Ever needed to know what a quest wants before you have it? Questbook answers one question for any quest in the game: what do I do for this quest? It works for quests you have never picked up, not just the ones in your quest log.
Back to Files

Questbook-v0.1.6

File nameQuestbook-Questbook-v0.1.6.zip
Uploader
PmptastyPmptasty
Uploaded
Oct 8, 2026
Downloads
147
Size
345.2 KB
Flavors
Classic TBCClassicForever
File ID
9094881
Type
R
Release
Supported game versions
  • 2.5.6
  • 1.60.1
  • 1.15.9

What's new

Questbook Changelog

[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.lua still carried --@curseforge-project-slug: libdbicon-1-0@. The BigWigs packager greps every packaged .lua for that marker and records the slug as an embedded library, which overrides .pkgmeta's required dependency (release.sh:1882-1889). Questbook.toc and Questbook_TBC.toc do not load the bundled copy (only Questbook_Camelot.toc does), 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 disable header. 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, name is the log's (client language), level the log's or else the database's, and zone is QuestData:GetQuestSummary(id).zoneName or nil. It reads through QuestLog: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.lua now 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 (from 85c2946).
  • Deferred collection (harness 515873b, inbox fa45bde6): the env turns wow.deferCollection(true) on once. M.reset now releases the previous load first (new M.release(): every Ace3 module, the widget, minimap, guild-sync and VersionCheck libraries, LibQuestDB, Questie's stand-in and QuestbookDB), then runs one wow.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_spec onward stayed live. At this pin (harness d640d37 silences an evicted Ace3 library's frames), the heap and live frame count after every file match the old run, and retention_spec passes.
  • The six _G.AceComm30Frame = nil lines in Tests/guildsync_spec.lua are gone (inbox 050f3f32). Every example there reloads AceComm through ace.forget or ace.reset, which now clear that global themselves.
  • The manual wow.menuModifiers clearing before frames.reset() stays. Harness 303bc99 replaces 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 or Questbook.code-workspace (every Lua.* 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. ExpandSkillHeader and GetQuestLogPushable joined the globals list. A .luarc.json was 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 QuestFrame event at load (YUI-Dialogue Code/Core.lua:420) and shows its own DUIQuestFrame, so the panel, a child of QuestFrame, never showed and Questbook fell back to chat. AcceptWarning.Host now picks the dialog on every offer: QuestFrame while it takes QUEST_DETAIL, otherwise DUIQuestFrame when 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 its frameOffsetX), and hides when that frame closes.
  • The host used to be decided once, at install. Dialogue UI's "disable in instances" setting hands QuestFrame its 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.lua asserts 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() and frames.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 to guildsync_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 in M.reset. The holder after guildsync_spec is 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_OUT now drops QuestDB's new retired hidden reason as well as unobtainable. 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:EffectiveLevel raises 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.lua adds Guide me (Questbook) or Stop guiding (Questbook) to the tracker's right-click menu, through Menu.ModifyMenu on MENU_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.ModifyMenu registrations 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.reset now clears wow.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:TrimToTarget cuts 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 through W:ShowTooltip / W:HideTooltip, with Chrome.ShowTooltip on 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 a headerTip, 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)". GuildOn now 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 onCellClick calls DetailPane: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") through col.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 within NEAR_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 in char.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:RecordMissing runs on every log record (Roster:Record). It keeps each quest the data source has no quest for in the account-wide global.missingQuests[<interface>][questId] (name, level, first-seen date), so each client's list stays separate. /questbook missing prints 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:ClientWaypoint reads C_QuestLog.GetNextWaypoint (map id and 0..1 x, y, documented in Forever's QuestLogDocumentation.lua:251) and GetNextWaypointText. Both are feature detected and pcalled. ActiveQuest:Set and Point guide 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:GetTownsfolk gains stations, 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.EmptyTownsfolk gains the key.
  • PlacesIndex:StationRows lists one row per cluster of points within STATION_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.AnchorBelow catches 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:TrackOf puts the spawn's entityId on the track when the location is the spawn itself. Tracker:Track asks Router:Approach for the walk, which is feature detected and pcalled. Router:Route makes 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 in Tests/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:Render on 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, and Render called Show on 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 in pendingShown, 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 in Tests/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 in Tracker:Waypoint. That rule waits until the player is more than twice their starting distance from the waypoint, plus Router.STRAY (100 yards). Walking away from a node 100 yards behind therefore kept the stale pointer for 300 yards.
  • The fix: Tracker:RouteFor records 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 than Router.REPLAN (25 yards) from that spot plans again from where the player is, which is what the /reload did. The stray rule is unchanged for the legs after the approach. Pinned in Tests/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' ## Title now 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 ## Category line.

The guild sync's libraries install with Questbook (2026-09-27)

  • .pkgmeta's required-dependencies gains deltasync, acecommqueue and libguildroster (TOGBankClassic's slugs). Questbook.toc hard-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.lua read the log only through the Vanilla bare globals (GetQuestLogTitle and friends). Forever's client carries the Mainline C_QuestLog instead: GetNumQuestLogEntries, GetInfo(index), GetLogIndexForQuestID, IsComplete, GetQuestObjectives, GetQuestWatchType and AddQuestWatch, 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 when C_QuestLog.GetInfo exists, and then the entries, the log index, the objective counters and the watch all go through C_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_spec drives a Forever-shaped C_QuestLog with 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) and Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua (MINOR 56), copied unchanged from the standalone v12.0.3. Questbook_Camelot.toc alone 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 (.pkgmeta must not ignore Libs); 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 Libs folder, put LibDBIcon-1.0 back on Questbook_Camelot.toc's Dependencies line, drop its OptionalDeps line, and empty FOREVER_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, on GUILD_ROSTER_UPDATE and GROUP_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_INSIDE names the three raids by area id, and SeenRaid matches 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@time per 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. Sightings merges 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.lua covers 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. Save refuses 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 / ReplayBroadcasts keep such a line and read it again on the next client event after the table arrives. Asked of DeltaSync (inbox 12ca628b), which delivered ParkBroadcast the 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.toc now 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 (IsInGuild and 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:Accept now 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 (from 7e9c2fe). Questbook's IsUnitOnQuest stand-in is gone, replaced by the harness's own. So is the tooltip stand-in, once the harness modelled the OnTooltipSetUnit handler Era's GameTooltip declares (asked in inbox ed87f83e, delivered as 2c4868e).
  • Twenty is_nil checks 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_IN is 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 next QUEST_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 in Player.dinged, and Player.Level never 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:StepName for 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 in attunements_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 as s. 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_CHANGED for 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:Changed for that, so the shared Party.lua names 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 in attunements_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, against debugprofilestop; 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:Build split into NewBuild/BuildStep so the list can be built in slices. Index.generation (bumped by Invalidate) 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 by Refresh) 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.generation was first nil until the first Refresh, 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 looped while on 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) and Modules/AttunementView.lua (the tab) are listed at the end of Questbook.toc and in neither sibling. The view adds its own entry to Window.TABS/Window.VIEWS and registers its own events frame, so Window.lua and Core.lua are unchanged and TBC and Forever show the same four tabs as before. env_spec now 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_spec re-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' PersistWindow now caps a window to the screen (ClampWindow, its MINOR 36). The harness screen is 1024 wide, narrower than Questbook's 1060 default, so core_spec and window_spec were 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:Knows and Router:FlightMasterNamed as "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 through Router.TAXI_ALIASES where 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.lua now reads every flight master out of QuestDB's Vanilla, TBC and Forever Paths.lua and 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, WoWAPITesting 7e9c2fe, 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.lua is deleted, and so is the /questbook source switch that chose between the two; /questbook source now just opens the window like any unknown word. Questie is gone from the ## OptionalDeps of 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:HoldQuestieAutoAccept and 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 at PLAYER_LOGIN. Peer Review confirmed the hook lands: Questie creates its AutoQuesting module when the file loads (Questie/Modules/Auto/AutoQuesting.lua:5) and its QUEST_DETAIL handler calls it through the module table (EventHandler.lua:153). Not verified in game.
  • Asked QuestDB (inbox 6786d5df) whether its QuestieSource.lua still finds Questie's curated rows now that Questie keeps its database in a separate QuestieDB addon.
  • 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:MissingPrerequisites handled negative pre-quest entries, and a comment said QuestDB passed Questie's signed lists through. It does not: QuestDB's QuestieSource.lua turns them into plain ids first. Player.ClassToken and 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.lua combines three sources, best first:
    1. 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.
    2. Questie users, when Questie is installed here: Questie's already-decoded QuestieComms.remoteQuestLogs is read, never written and never decoded from Questie's wire format, which is its own serializer and is changing.
    3. 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.
  • Every name is normalised to Name-Realm before 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, the OnTooltipSetUnit hook 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 to ObjectiveData[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.GetItemInfo only; the bare GetItemInfo fallback 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): TooltipDataProcessor when GameTooltip.GetPrimaryTooltipData exists (Forever), otherwise HookScript("OnTooltipSetUnit") only after HasScript confirms it. Forbidden tooltips are skipped, and nothing is added inside instances on Forever, as Questie's TooltipHandler.lua:13 does.
    • Raids: IsUnitOnQuest is asked only through the member's party1-party4 token, 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 to Who and HeldQuests. 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.
  • Specs: party_spec and unittooltip_spec. The harness models neither IsUnitOnQuest nor the tooltip's OnTooltipSetUnit, so Tests/env_questbook.lua stands in IsUnitOnQuest and sets a stand-in OnTooltipSetUnit handler on GameTooltip. 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.lua 170/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 Forever TooltipDataProcessor route, position matching on a non-English client, and IsUnitOnQuest with 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.
  • level is 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 as QuestbookStrip): 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 wherever FilterStrip.Library() finds it; FilterStrip.Layout stays 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's TOGBankSearchBox; the matching rule is the library's SearchMatch (every word somewhere in the fields), with the same rule in SearchBox.Match for 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.Create uses it wherever AceGUI:GetWidgetVersion("LAGW-SearchBox") answers, and falls back to QuestbookSearchBox on 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.lua now 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:Subject turns 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:ForPlayer and Index:Filter take 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_spec over 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:Sample now 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.toc and Questbook_TBC.toc add LibDBIcon-1.0 to ## Dependencies and no longer load libs\LibDataBroker-1.1.lua or libs\LibDBIcon-1.0.lua. The standalone brings LibDataBroker-1.1 with it (its embeds.xml). .pkgmeta requires the CurseForge slug libdbicon-1-0.
  • WoW Forever too, and the embedded copy is deleted. I first kept libs\ for Questbook_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 the libs\ folder (LibDataBroker-1.1 and LibDBIcon-1.0) is gone. No Lua changed.
  • Tests/env_spec.lua again requires all three manifests to load the same files and dependencies, and asserts no manifest loads anything from libs/ 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 /reload with 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. target is 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 numeric world.continent/x/y, or when the addon is not initialised.
  • Questbook:StopTracking() and Questbook:IsTracking(), for the stop icon on the same window (contract LIBREQ-QB-STOPTRACKING). StopTracking is the universal stop that the window's own stop icon and /questbook hud off already 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 by Tests/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 models GetQuestLogQuestText() on the quest log (Questbook's request 63f4ea90). The stand-in Tests/env_questbook.lua carried 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.

This mod has no additional files