Six things that looked like they worked
None of these fails loudly and two of them correct themselves if you reload, which is why five were found by reading rather than by anybody reporting them. The reply that lost its author is the one worth knowing about: the frame that replaces a bubble when a reply lands was looking the models up as nobody, and "no user" answers "no models" rather than "all models" -- so every finished reply swapped the model's avatar for the plain mark and put the instance's name where the model's should be, until the next page load put it back. Beside it: a concurrency quota enforced on two of the six paths that start a reply, including neither of the two most used; a custom theme whose success and warning colours moved the text and left the background behind; a phone shell sized to one viewport inside a document sized to another, which is the reported scroll past the bottom of Settings; a whole conversation's Markdown rendered on every page load and read by nothing; a skip guard inert since it was written; and an endpoint nothing has ever called. The scroll fix folded five near-identical scroller rules into one, which is also where the containment they were all missing now lives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,14 +15,10 @@
|
||||
that one, silently, while the reader was lost in the other. Under
|
||||
`.admin-scroll` the body is now an ordinary block and the page scrolls as one.
|
||||
*/
|
||||
.admin-scroll,
|
||||
.main > .tabs > .tabs__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
/* What makes one of these scroll is `.scroll-region` in app.css, which both of
|
||||
these selectors are listed in. Named there so the four declarations exist
|
||||
once; named *here* is the reasoning above, which is about which element is
|
||||
the scroller on which screen rather than about how a scroller behaves. */
|
||||
|
||||
.page,
|
||||
.admin-page {
|
||||
|
||||
@@ -18,6 +18,33 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
A page built around the shell never scrolls its own document.
|
||||
|
||||
`.shell` is `100dvh` -- the *dynamic* viewport, which is what you can actually
|
||||
see -- while `html` and `body` above are `100%`, which resolves against the
|
||||
initial containing block and is the *large* viewport, the one you get with the
|
||||
browser's toolbar retracted. On a desktop those are the same number and this
|
||||
rule does nothing. On a phone they differ by the height of the toolbar, and
|
||||
the difference is a document taller than its own window: you scroll past the
|
||||
bottom of the sidebar and the main column into bare background, and because
|
||||
every gesture retracts or extends the toolbar the shell resizes underneath you
|
||||
and it never settles.
|
||||
|
||||
Reported on /settings, true of every page with a shell. `:has()` rather than a
|
||||
class because the shell is what decides this, not the route -- the auth, error
|
||||
and offline pages have no shell and genuinely do scroll their document, and
|
||||
they must keep doing so.
|
||||
*/
|
||||
html:has(body > .shell),
|
||||
body:has(> .shell) {
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
/* A flick that reaches the end of an inner scroller stops there rather than
|
||||
pulling the page around behind it. */
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
/*
|
||||
The `hidden` attribute has to win.
|
||||
|
||||
@@ -367,6 +394,39 @@ button, input, textarea, select {
|
||||
.badge--danger { background: var(--danger-soft); color: var(--danger); }
|
||||
.badge--warning { background: var(--warning-soft); color: var(--warning); }
|
||||
|
||||
/* --- Scroll regions --------------------------------------------------------
|
||||
The four declarations that make an element *the* scroller, written once.
|
||||
|
||||
They were spelled out five times -- the sidebar's list, the thread, the
|
||||
inspector, the canvas and (in admin.css) the tabs and admin pages -- and
|
||||
agreed on three of them. The fourth, `overscroll-behavior`, was on the
|
||||
sidebar alone, with a good comment explaining why it was needed there. It is
|
||||
needed everywhere for the same reason: a flick that reaches the end of a
|
||||
scroller chains to whatever is behind it, and behind these is the shell,
|
||||
which does not scroll -- so what the gesture produces is not a scrolled page
|
||||
but a rubber-band into blank background, which reads as the layout having
|
||||
come loose.
|
||||
|
||||
`min-height: 0` is the half that is load-bearing rather than cosmetic: a flex
|
||||
child will not shrink below its content without it, so a scroller missing it
|
||||
grows its parent instead of scrolling inside it. `.thread-scroll` relied on a
|
||||
scroll container's automatic minimum size to get away with omitting it, which
|
||||
is true and is not something the next person should have to know. */
|
||||
.scroll-region,
|
||||
.sidebar__scroll,
|
||||
.inspector__body,
|
||||
.canvas__body,
|
||||
.thread-scroll,
|
||||
.admin-scroll,
|
||||
.main > .tabs > .tabs__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
|
||||
/* --- Application shell ----------------------------------------------------- */
|
||||
.shell { display: flex; height: 100dvh; overflow: hidden; }
|
||||
|
||||
@@ -412,18 +472,9 @@ button, input, textarea, select {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* A `.scroll-region`; only the padding is its own. */
|
||||
.sidebar__scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
/* A flick past the end of the list stops there rather than chaining to
|
||||
whatever is behind it. The shell is `overflow: hidden`, so what chaining
|
||||
produced was not a scrolled page but a rubber-band into blank background --
|
||||
which reads as the sidebar having come loose from the layout. */
|
||||
overscroll-behavior: contain;
|
||||
padding: 0 var(--sp-2) var(--sp-3);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
|
||||
.sidebar__footer {
|
||||
@@ -491,12 +542,7 @@ button, input, textarea, select {
|
||||
}
|
||||
|
||||
.inspector__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: var(--sp-4);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
|
||||
.inspector__heading {
|
||||
@@ -789,8 +835,8 @@ body.is-resizing .canvas__body { pointer-events: none; }
|
||||
}
|
||||
|
||||
.canvas__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
/* Both axes, unlike every other scroll region: nothing re-wraps a source
|
||||
line, so it has to be reachable sideways. */
|
||||
overflow: auto;
|
||||
padding: var(--sp-3);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
*/
|
||||
|
||||
/* --- Thread --------------------------------------------------------------- */
|
||||
/* A `.scroll-region` (app.css); the smooth behaviour is this one's own, because
|
||||
this is the scroller something is repeatedly scrolled *to* -- the newest
|
||||
message, a jump back to the bottom -- and the others are not. */
|
||||
.thread-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
|
||||
.thread {
|
||||
|
||||
@@ -25,16 +25,12 @@
|
||||
</p>
|
||||
<div class="compacted__body">
|
||||
{% for message in compacted %}
|
||||
{% with body_html = bodies.get(message.id, "") %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endwith %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% for message in messages %}
|
||||
{% with body_html = bodies.get(message.id, "") %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endwith %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -29,7 +29,5 @@
|
||||
{% endif %}
|
||||
|
||||
{% for message in messages %}
|
||||
{% with body_html = bodies.get(message.id, "") %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endwith %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -83,9 +83,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% for message in messages %}
|
||||
{% with body_html = bodies.get(message.id, "") %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endwith %}
|
||||
{% include "chat/_message.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user