Four follow-ups to #21. The first three were written before that PR merged
but never made it in — it merged at acfc81f, so they are still absent from main. Rebased onto the merge, so this is the full remainder.
The theme of the first three is the same: a change to the mobile fix from #21
can be applied correctly and still have no visible effect, with nothing in
the path reporting a problem.
remote.conf is read once, at process start
dejima-ttyd reads TTYD_FONT_SIZE and execs ttyd with it in argv, so
editing the file without restarting the unit leaves the old size serving. The
README never said to restart.
A failed nginx reload keeps the old config and still reports success
systemctl reload nginx succeeds when the new config does not parse — the old
one keeps running. nginx-light is built without the sub module, so an
unknown sub_filter directive is indistinguishable from a directive that ran
and did nothing. The docs claimed the opposite; they now point at nginx -t.
nginx -V and nginx -t say "command not found" on a healthy box
Debian and Ubuntu install to /usr/sbin, which is on root's PATH and not on
an ordinary user's. The bare commands these docs told the operator to run
point at a missing package when the real problem is a missing path — the
wrong thing to go and check. Absolute paths given throughout.
ttyd's page has no cache validator, and the proxy now rewrites the body
src/http.c sets status, content-type, content-encoding and content-length
and nothing else — no Cache-Control, no ETag, no Last-Modified — so a
browser may cache it indefinitely. Harmless while the body is served
verbatim; a trap once sub_filter rewrites it, because a phone holding the
un-injected copy renders it forever and no later change to the injection ever
reaches a browser that has already been there. Nothing is given up by
refusing the cache: with no validator upstream a revalidation could never come
back 304, and the page is still gzipped to the client, since clearing Accept-Encoding applies only to the request made to ttyd. Covered in test/remote.bats alongside the Accept-Encoding assertion, because the
filter and the things that keep it observable are only correct together.
Typing from a phone replays text you already typed
New README section. Two things place this in the browser rather than in
anything here: the duplicates are real input you can backspace over, not a
redraw artifact, and the transport is TCP, which cannot deliver the same bytes
twice. It reproduces on demand — a long run of digits never duplicates, while
ordinary words duplicate on every correction the keyboard makes.
The mechanism is xterm.js re-sending the entire contents of its hidden input
element when a soft keyboard replaces a span it has already committed,
instead of sending the difference. Nothing on this side reaches it: CoreBrowserTerminal.ts already sets autocorrect, autocapitalize and spellcheck to off on that element as it creates it, and Android ignores all
three — so injecting those same attributes via sub_filter, which is the
obvious-looking fix, would have been a no-op that looked like one. This is
documented rather than worked around deliberately: it is a general
ttyd/xterm.js interaction with predictive keyboards, not a dejima bug.
The recommendation is Unexpected Keyboard, and it earns it on the modifier
keys more than on the bug — without a real Ctrl there is no tmux prefix, and
Esc and the arrows are hard to do without in anything interactive. Its word
suggestions are opt-in behind a dictionary you download yourself, which is
why the wording says that rather than "no prediction": the feature does ship,
and claiming otherwise in a public README would be false.
Testing
bats test/audit.bats test/project.bats test/proxy.bats test/remote.bats test/static.bats → 301 passing, 0 failures on the rebased branch. The
pre-existing BW01 warning from test/static.bats:248 is unchanged.
The cache-control assertion was verified the only way that means anything:
by removing the directive and watching the test fail.
test/runtime.bats was not run — it builds the Docker image in setup_file and there is no docker binary in the sandbox this was written
in. It needs a run on the host before merge.
The ttyd claims are checked against committed source rather than inferred: src/html.h at 1.7.7 for the single literal lowercase <head> and zero
viewport metas that make the sub_filter pattern correct, and src/http.c
for the absent cache headers.
**Summary generated by LLM agent**
Four follow-ups to #21. The first three were written before that PR merged
but never made it in — it merged at `acfc81f`, so they are still absent from
`main`. Rebased onto the merge, so this is the full remainder.
The theme of the first three is the same: a change to the mobile fix from #21
can be applied *correctly* and still have no visible effect, with nothing in
the path reporting a problem.
### `remote.conf` is read once, at process start
`dejima-ttyd` reads `TTYD_FONT_SIZE` and execs `ttyd` with it in argv, so
editing the file without restarting the unit leaves the old size serving. The
README never said to restart.
### A failed nginx reload keeps the old config and still reports success
`systemctl reload nginx` succeeds when the new config does not parse — the old
one keeps running. `nginx-light` is built without the sub module, so an
unknown `sub_filter` directive is indistinguishable from a directive that ran
and did nothing. The docs claimed the opposite; they now point at `nginx -t`.
### `nginx -V` and `nginx -t` say "command not found" on a healthy box
Debian and Ubuntu install to `/usr/sbin`, which is on root's PATH and not on
an ordinary user's. The bare commands these docs told the operator to run
point at a missing *package* when the real problem is a missing *path* — the
wrong thing to go and check. Absolute paths given throughout.
### ttyd's page has no cache validator, and the proxy now rewrites the body
`src/http.c` sets status, content-type, content-encoding and content-length
and nothing else — no `Cache-Control`, no `ETag`, no `Last-Modified` — so a
browser may cache it indefinitely. Harmless while the body is served
verbatim; a trap once `sub_filter` rewrites it, because a phone holding the
un-injected copy renders it forever and no later change to the injection ever
reaches a browser that has already been there. Nothing is given up by
refusing the cache: with no validator upstream a revalidation could never come
back 304, and the page is still gzipped to the client, since clearing
`Accept-Encoding` applies only to the request made to ttyd. Covered in
`test/remote.bats` alongside the `Accept-Encoding` assertion, because the
filter and the things that keep it observable are only correct together.
### Typing from a phone replays text you already typed
New README section. Two things place this in the browser rather than in
anything here: the duplicates are real input you can backspace over, not a
redraw artifact, and the transport is TCP, which cannot deliver the same bytes
twice. It reproduces on demand — a long run of digits never duplicates, while
ordinary words duplicate on every correction the keyboard makes.
The mechanism is xterm.js re-sending the entire contents of its hidden input
element when a soft keyboard *replaces* a span it has already committed,
instead of sending the difference. Nothing on this side reaches it:
`CoreBrowserTerminal.ts` already sets `autocorrect`, `autocapitalize` and
`spellcheck` to off on that element as it creates it, and Android ignores all
three — so injecting those same attributes via `sub_filter`, which is the
obvious-looking fix, would have been a no-op that looked like one. This is
documented rather than worked around deliberately: it is a general
ttyd/xterm.js interaction with predictive keyboards, not a dejima bug.
The recommendation is Unexpected Keyboard, and it earns it on the modifier
keys more than on the bug — without a real Ctrl there is no tmux prefix, and
Esc and the arrows are hard to do without in anything interactive. Its word
suggestions are opt-in behind a dictionary you download yourself, which is
why the wording says that rather than "no prediction": the feature does ship,
and claiming otherwise in a public README would be false.
## Testing
`bats test/audit.bats test/project.bats test/proxy.bats test/remote.bats
test/static.bats` → **301 passing, 0 failures** on the rebased branch. The
pre-existing `BW01` warning from `test/static.bats:248` is unchanged.
The cache-control assertion was verified the only way that means anything:
by removing the directive and watching the test fail.
`test/runtime.bats` was **not** run — it builds the Docker image in
`setup_file` and there is no docker binary in the sandbox this was written
in. It needs a run on the host before merge.
The ttyd claims are checked against committed source rather than inferred:
`src/html.h` at 1.7.7 for the single literal lowercase `<head>` and zero
viewport metas that make the `sub_filter` pattern correct, and `src/http.c`
for the absent cache headers.
Both halves of the mobile fix can be applied correctly and still have no
visible effect, for reasons nothing reports:
- remote.conf is read once, at process start. dejima-ttyd reads
TTYD_FONT_SIZE and execs ttyd with it in argv, so editing the file
without restarting the unit leaves the old size serving. The README
never said to restart, and there is no warning anywhere in the path.
- an nginx reload whose config does not parse keeps the OLD config
running while systemctl reload still reports success. An unknown
sub_filter directive - nginx-light is built without the sub module -
is therefore indistinguishable from a directive that ran and did
nothing. The docs claimed the opposite; point at nginx -t instead.
Verified against ttyd 1.7.7's committed src/html.h: the served page
contains exactly one literal lowercase <head> and zero viewport metas,
so the sub_filter pattern itself is correct.
Debian and Ubuntu install nginx to /usr/sbin, which is on root's PATH and
not on an ordinary user's, so the bare 'nginx -V' and 'nginx -t' these
docs told the operator to run answer 'command not found' on a box that is
serving happily - pointing at a missing package rather than a missing
path, which is the wrong thing to go and check.
ttyd sends its page with no Cache-Control, no ETag and no Last-Modified -
src/http.c sets status, content-type, content-encoding, content-length and
nothing else - so a browser may cache it for as long as it likes. That is
harmless while the body is served verbatim, and a trap once sub_filter
rewrites it: a phone holding the un-injected copy renders it forever, so
the viewport fix reads as broken long after it works, and no later change
to the injection ever reaches a browser that has already been there.
Nothing is given up by refusing it. With no validator upstream a
revalidation could never come back 304, and the page is still gzipped to
the client - clearing Accept-Encoding applies only to the request made to
ttyd, not to the response nginx sends on.
Coupled in the test alongside Accept-Encoding, for the same reason: the
filter and the things that keep it observable are only correct together.
Verified by removing the directive and watching the test fail.
Typing into the web front door from a phone replays text you already
typed, a batch at a time. Two things place it in the browser rather than
in anything here: the duplicates are real input you can backspace over,
not a redraw artifact, and the transport is TCP, which cannot deliver the
same bytes twice. It also reproduces on demand - a long run of digits
never duplicates, while ordinary words duplicate on every correction the
keyboard makes.
The mechanism is xterm.js re-sending the whole contents of its hidden
input element when a soft keyboard *replaces* a span it has already
committed, rather than sending the difference. Nothing on this side
reaches it: CoreBrowserTerminal.ts already sets autocorrect,
autocapitalize and spellcheck to off on that element as it creates it,
and Android ignores all three - so injecting those same attributes via
sub_filter, the obvious-looking fix, would have been a no-op that looked
like one.
So document it and recommend a keyboard with nothing to correct against.
Unexpected Keyboard earns the recommendation on the modifier keys more
than on the bug - without a real Ctrl there is no tmux prefix, and Esc
and the arrows are hard to do without in anything interactive. Its
suggestions are opt-in behind a dictionary you download yourself, which
is why the wording says that and not "no prediction": the feature does
ship.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary generated by LLM agent
Four follow-ups to #21. The first three were written before that PR merged
but never made it in — it merged at
acfc81f, so they are still absent frommain. Rebased onto the merge, so this is the full remainder.The theme of the first three is the same: a change to the mobile fix from #21
can be applied correctly and still have no visible effect, with nothing in
the path reporting a problem.
remote.confis read once, at process startdejima-ttydreadsTTYD_FONT_SIZEand execsttydwith it in argv, soediting the file without restarting the unit leaves the old size serving. The
README never said to restart.
A failed nginx reload keeps the old config and still reports success
systemctl reload nginxsucceeds when the new config does not parse — the oldone keeps running.
nginx-lightis built without the sub module, so anunknown
sub_filterdirective is indistinguishable from a directive that ranand did nothing. The docs claimed the opposite; they now point at
nginx -t.nginx -Vandnginx -tsay "command not found" on a healthy boxDebian and Ubuntu install to
/usr/sbin, which is on root's PATH and not onan ordinary user's. The bare commands these docs told the operator to run
point at a missing package when the real problem is a missing path — the
wrong thing to go and check. Absolute paths given throughout.
ttyd's page has no cache validator, and the proxy now rewrites the body
src/http.csets status, content-type, content-encoding and content-lengthand nothing else — no
Cache-Control, noETag, noLast-Modified— so abrowser may cache it indefinitely. Harmless while the body is served
verbatim; a trap once
sub_filterrewrites it, because a phone holding theun-injected copy renders it forever and no later change to the injection ever
reaches a browser that has already been there. Nothing is given up by
refusing the cache: with no validator upstream a revalidation could never come
back 304, and the page is still gzipped to the client, since clearing
Accept-Encodingapplies only to the request made to ttyd. Covered intest/remote.batsalongside theAccept-Encodingassertion, because thefilter and the things that keep it observable are only correct together.
Typing from a phone replays text you already typed
New README section. Two things place this in the browser rather than in
anything here: the duplicates are real input you can backspace over, not a
redraw artifact, and the transport is TCP, which cannot deliver the same bytes
twice. It reproduces on demand — a long run of digits never duplicates, while
ordinary words duplicate on every correction the keyboard makes.
The mechanism is xterm.js re-sending the entire contents of its hidden input
element when a soft keyboard replaces a span it has already committed,
instead of sending the difference. Nothing on this side reaches it:
CoreBrowserTerminal.tsalready setsautocorrect,autocapitalizeandspellcheckto off on that element as it creates it, and Android ignores allthree — so injecting those same attributes via
sub_filter, which is theobvious-looking fix, would have been a no-op that looked like one. This is
documented rather than worked around deliberately: it is a general
ttyd/xterm.js interaction with predictive keyboards, not a dejima bug.
The recommendation is Unexpected Keyboard, and it earns it on the modifier
keys more than on the bug — without a real Ctrl there is no tmux prefix, and
Esc and the arrows are hard to do without in anything interactive. Its word
suggestions are opt-in behind a dictionary you download yourself, which is
why the wording says that rather than "no prediction": the feature does ship,
and claiming otherwise in a public README would be false.
Testing
bats test/audit.bats test/project.bats test/proxy.bats test/remote.bats test/static.bats→ 301 passing, 0 failures on the rebased branch. Thepre-existing
BW01warning fromtest/static.bats:248is unchanged.The cache-control assertion was verified the only way that means anything:
by removing the directive and watching the test fail.
test/runtime.batswas not run — it builds the Docker image insetup_fileand there is no docker binary in the sandbox this was writtenin. It needs a run on the host before merge.
The ttyd claims are checked against committed source rather than inferred:
src/html.hat 1.7.7 for the single literal lowercase<head>and zeroviewport metas that make the
sub_filterpattern correct, andsrc/http.cfor the absent cache headers.