Basically, what I have is a "fake" form, meant to mimic a login screen with username and password. It's contained within a custom class, in order to disable Harlowe's fading in text (the effect looks bad on fixed interface elements). Removing this div does not fix the autofocus issue.
<div id="interface" class="nofade">
The username field is first, and has autofocus:
<input id="username" type="text" maxlength="20" oninput=" populatePassword()" onKeyPress="return checkSubmit(event,link)"/ autofocus>
Additional functions here:
populatePassword adds asterisks to the password field immediately below once the user starts entering a username. (The "password" box is entirely for cosmetic effect; I don't want to confuse the player into thinking it's a puzzle they don't know the answer to.) It doesn't cause the autofocus issue -- removing it makes no difference, the field still loses focus.
checkSubmit "submits" the form (i.e., advances to the next passage) if the user has pressed Enter on the field. event is standard, link is custom. Removing this also does not affect the autofocus issue in any way.
var link = document.querySelector('tw-link[passage-name="Hub"]');
I'm currently using a workaround as follows. It's not ideal -- there's a split-second blip between when the autofocus is lost and when it's re-applied, which is visible if you're looking for it -- but does allow the user to continuously type without noticeable interruption:
// hack to prevent harlowe's transitions from jacking focus
var timeoutoverride = 808; // ~0.8 seconds
var focustimer = setTimeout(function() {
document.getElementById("username").focus();
}, timeoutoverride);
(There's a corresponding clearTimeout anywhere the user can advance to the next passage, in case they manage to do it in less than 0.8 seconds.)