The applyMode() function reads/writes anchorError, lastSnapshot, and other
let-bound state. Function declarations are hoisted, so applyMode() can fire
from inside the dialog.mount() / applyMode() call sequence at the top of
start() — but the let bindings themselves are not initialized until
execution reaches their declaration line, which came later.
When prefs.mode === 'anchored' and findAnchorElement() returns null, the
new 'anchor missed' branch writes to anchorError and calls render(). Both
access anchorError before its let binding is initialized, throwing
ReferenceError: Cannot access 'anchorError' before initialization.
Move all four let declarations (lastSnapshot, lastAttr, lastDelta,
anchorError) to the top of start(), before dialog.mount() and applyMode().
Function declarations are unaffected — they are hoisted regardless.
Mirror the three source-level fixes in the embedded copies inside
torn-attribute-tracker.user.js:
1. _wireHeaderDrag: add the .tat-header closest() guard so the bundle
no longer steals focus from inputs/selects.
2. findAnchorElement: replace the narrow 'button[name="train"]' query
with the priority-ordered candidate list and the gym-panel last-
ditch fallback.
3. Inline anchor-error UX: add the anchorError state, include it in
the snapshot, surface it via applyMode, render the note with the
.tat-anchor-err class, and add the corresponding CSS rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
findAnchorElement used a narrow selector ('button[name="train"],
a[href*="train"]') that often missed Torn's actual gym page DOM.
When it missed, applyMode fell through to dialog.setMode('free'),
snapping the dialog to the default bottom-right position — which the
user perceived as a 'bounce' when clicking 'Above training UI'.
Widen the selector to a priority-ordered candidate list and prefer the
form ancestor of any matched element. As a last-ditch, look for a form
inside the gym panel. This covers more of Torn's gym-page variations.
When the anchor selector still misses, do NOT snap to the default free
position. Instead, keep the dialog where it is, set anchorError on the
state, and let the dialog render an inline note so the user gets
visible feedback explaining what happened.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dialog's mousedown listener was attached to the whole root and
unconditionally called preventDefault(), which blocked the target
element from receiving focus. As a result, the custom target <input>
and the milestone <select> could never be focused.
Only initiate drag (and only preventDefault) when the mousedown is on
the .tat-header bar. This lets the user click into inputs, selects, and
buttons inside the dialog body, while still allowing the dialog to be
dragged from the title bar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Introduced a new userscript for torn.com to assist players in planning attribute training.
- Document outlines the purpose, scope, architecture, dialog content, placement modes, error handling, testing, and file layout.
- Details on data handling, UI interactions, and user preferences included.