Previously the .tat-root.tat-anchored rule centered the dialog with a
720px max-width and no extra border, which made it look like a floating
widget squeezed into the page rather than an embedded panel. The
Torn gym page reference is a full-width panel with a thin dark border
and a red top accent line.
Changes:
- margin: 0 0 12px 0 (full-width, no centering)
- max-width: none (span the content area)
- border-radius: 0 (Torn panels are flat, not rounded)
- border: 1px solid #444 with border-top: 2px solid #c00 (red accent)
- padding: 16px 20px (more breathing room, matching Torn panels)
- .tat-root.tat-anchored .tat-header { cursor: default } (drag is
disabled in anchored mode, so the move cursor was misleading)
Free-floating mode is unchanged: the .tat-root base rule keeps its
rounded shadowed look and .tat-header keeps cursor: move.
Mirrored into the embedded TAT_STYLE in the bundle to keep the source
and bundle in lockstep.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The embedded currentAttribute() in the bundle was returning gym:
tatEsc(gym), but the render template in Dialog.render also escapes with
tatEsc(s.gym). The double-escape turned 'George's' into 'George&#39;s',
which the browser decoded to the visible text 'George's'.
The src/dom.js source does NOT pre-escape (returns the raw gym string and
lets the render template handle escaping once). This commit restores the
mirror in the embedded dom.js so the bundle matches the source.
No changes to src/dom.js (it was already correct).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors the three src/ changes into the embedded copy in
torn-attribute-tracker.user.js:
1. Hoist the four let bindings (lastSnapshot, lastAttr, lastDelta,
anchorError) to the top of start(), before dialog.mount() and
applyMode(), so the anchor-miss branch of applyMode() can write
anchorError without tripping the TDZ.
2. Default the floating dialog to bottom-left (left: 20px, bottom: 20px)
in both Dialog.mount() and Dialog.setMode()'s 'free' branch.
3. One-time migration: if prefs.pos has any non-zero coordinate on load
(a residue of the bottom-right era), reset it to {x: 0, y: 0} and log
to the console. Stored position from any subsequent drag is preserved.
4. Wrap the start() body in try/catch and log failures to the console,
so an unexpected error (e.g. TornTools conflict, future regressions)
does not prevent the dialog from appearing.
All four changes are byte-equivalent to the corresponding src/ changes;
the build-time drift tests in tests/build.test.js still pass.
When the dialog first appears (mount) and when it switches to 'free' mode
(setMode), pin it to the bottom-left corner of the viewport (left: 20px,
bottom: 20px) rather than the previous bottom-right default. The header
drag handler still uses left/top for the new position, so this change
flows through cleanly on subsequent drags.
Note: existing users with a saved pos.x/pos.y in localStorage will still
see the dialog at the old transform-offset position until pos is reset
in a follow-up migration (see next commit).
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.