-
Notifications
You must be signed in to change notification settings - Fork 0
Import
- Left rail
$ importfield (280px rail, not route, not overlay) - Top-right
[ Sync / Import ]->Import addon (wb1!) - Settings -> Import (same decoder)
- Hotkey
ianywhere. Opens rail tab if collapsed.
- Click Import or press
i. Must be user gesture fornavigator.clipboard.readText(). - If clipboard is
wb1!length >10 -> auto-fill preview. Zero paste needed. - Otherwise text area focused "Paste wb1! here Ctrl+V" -> debounce 150ms -> preview.
- Preview sentence (rail cannot hold 8 columns): "6 characters, 1 over 3 days, Enter to import" plus rows with dots, hover per-section.
- Enter confirms ->
POST /api/addon/import{ bundle:"wb1!..." }-> D1character_addon_cacheandwarband_bank_cacheupsert peruser_id+char_key. - Receipt: one line per character dot + gold printed in waves like
$output, held 6 seconds, decayed in 4 steps, removed. Block returns to original height. - Nothing reloads. Only that line is stamped. Reload would discard receipt (what the overlay version did).
Client-side before D1 write:
- Not
wb1!-> ifwb0!then "That is Camp DNA, not inventory, use Share tab" else "Does not look like wb1!, run /warband copy again" - Over 20 characters or over 1 MB decoded -> "Too large, try single-character"
- Missing characters not deleted -> keeps old snapshot aging red. Staleness lowers confidence, never deletes.
- One character 39 KB JSON 8.6 KB wire, 6 154 KB / 26 KB, 20 cap 474 KB / 73 KB. 4-7 KB figure is slim with no per-item lists.
src/lib/warband-import.ts is pure, testable via Vitest, no DOM or D1:
-
WIRE_PREFIX='wb1!',DNA_PREFIX='wb0!',MAX_CHARACTERS=20,MAX_DECODED_BYTES=1_048_576 - Base64url -> bytes ->
DecompressionStream('deflate-raw')notdeflate(zlib header addon does not emit, use raw orpako.inflateRaw) - Throws on invalid input.
Web tests src/__tests__/warband-import.test.ts. Addon vectors docs/contract/vectors/v1-min.wb1.
Preview is rail-width decision about a paste that has not happened. Facts are what the app reads after paste.
warband-import.ts exports:
-
freshness(seenSec, nowMs)-> dot from seconds stamp. -
freshnessMs(seenMs, nowMs)-> same question from already converted milliseconds.deriveFacts()converts once so downstream holds ms only. -
freshnessVar(dot)-> token map. One map so rail receipt and roster columns agree. -
deriveFacts(CharacterObject)->AddonFactswith gold, free slots, keystone, vault buckets, consumables, currencies, andseenMsin ms. Conversion seconds to ms happens exactly once here. -
purse(facts, bankGold)-> summed character gold plus bank gold with reported count. -
currenciesNearCap(facts, {lastResetMs})-> account-wide only, deduped by id, freshest reading wins, weekly caps dropped if before last reset. -
findItems(characters, warbank, ids)-> every stack of ids by character and container. Bank counted once, hit stamped from containers actually matched.
lib/addon-cache.ts is the only D1 reader: readAddonSnapshot(db, userId) -> map of CharacterObject, warbank, importedAtMs. Degrades to empty on schema drift, never throws for drift. GET /api/addon/data returns facts, never calls Blizzard, never 401s.
Facts carry no policy. Whether a nine day old vault may be shown is consumer decision. tonight.ts discards vault before reset, roster dots age and shows anyway. Both are correct for their surface.
Where is my stuff is the feature per-item lists exist for, and the reason MAX_DECODED_BYTES is 1 MB.
Addon stores {id, count} with no name. Name search is two steps, only first touches network:
- Resolve words to ids against
/data/wow/search/itemon a ClientCredentials token. Region independent on oauth.battle.net. - Match ids against stored bags, bank, reagent bank, warbank tabs via
findItems(). Pure, inwarband-import.ts.
Resolving query not inventory is why it is affordable. Full warband holds thousands of stacks across hundreds of ids. Resolving inventory would be one call per item. Resolving query is one per search and does not grow with warband.
Numeric query skips step one. That is degradation path. When Game Data unreachable route returns nameSearch: 'unavailable' and UI says "name lookup unavailable, search by item id" rather than empty warband.
Counting rules:
- Warbank counted once, not per character. One vault, same reason it has own table.
- Hit stamped from containers matched in. Bank-only hit takes
seenAt.banknever borrows bag stamp.
Filter composes with other predicates on /characters. held is SortKey excluded from persisted sort keys so restoring sort over absent column cannot show every row reading 0.
Rail dispatches wb:import on success and surfaces that read facts re-fetch. Nothing reloads still holds for the block, but camp plan left showing pre-import advice is worse than reorder. This is one moment reorder under cursor is not surprise. It is what user pressed enter to cause. Announced not reached for, because rail has no business knowing which surface is mounted.
Any element whose raw text begins with // inside an Astro expression is deleted from output silently. Compiler reads // as line comment. No error.
Fix: never write literal // in .cmt text. Mark is .cmt::before CSS. Literal doubles when rendered and deletes when absent. Always a defect.
- HTTPS and user gesture required. Warband.pro is HTTPS so Chrome and Edge work.
- Never polling
readText, only inside click. - Do not auto-submit. User confirms after seeing staleness.
- Firefox and Safari are stricter -> fallback to text area
Ctrl+V.
After confirm, scorer re-reads consumables rollup (tonight.ts, warband-import.ts types). Freshness changes block to grey text.
Maintained alongside Warband.pro App and Addon. Syncs on push. Companion for Warband.pro. MIT. Draft version 0 until CurseForge live.
Start
Integration
Internals