| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The chat could not be read back: any wheel gesture was undone in the frame
it happened in, and the "jump to latest" button never appeared.
None of the pins in ChatPanel are at fault -- every one of them is guarded
by "only if the reader is at the bottom". The reader never got to stop
being at the bottom.
fit() set the panel's height, read documentElement.scrollHeight back and
subtracted the overflow, so the document alternately did and did not
overflow the window. The page scrollbar appeared and vanished with it and
visualViewport fired resize at every pass -- the event fit() is bound to.
It therefore re-entered itself for the life of the panel: measured at 240
firings in two seconds on a page nobody was touching, against 2 for a bare
document. Each pass ran fitAndPin, which re-pinned the list to the bottom
before the scroll event that would have recorded the gesture was delivered
a frame later, so atBottomRef never went false.
- fit() learns the space below the panel once and remembers it on the
element instead of re-deriving it by writing and measuring back. At the
steady state it writes nothing, so it produces no resize. A real window
resize or an orientation change forgets the learnt value and measures
again (the page under the panel may have reflowed); visualViewport
deliberately does not, since a phone fires it constantly.
- The scroll-to-bottom is now scoped to *arrival*, which is all it was ever
for: opening the group, or coming back to the Chat tab, including the
thumbnails and link-preview cards that keep growing the list for a second
afterwards. It ends when the reader takes hold of the scroll, and the
ResizeObserver disconnects there.
- That release is recorded from the gesture (wheel/touchmove/pointerdown/
keydown), not from the scroll event, which arrives too late to protect
anything.
Unchanged: landing on the newest message, following new messages while
already at the bottom, the "load older" anchor and the unread marker.
tests/harness/chat_scroll_probe.py mounts the real ChatPanel in a browser
and reads a conversation back; test_chat_scroll_up.py asserts against it.
With the fix reverted, five of its six tests fail and the sixth -- landing
on the newest message -- still passes, which is the property that must not
have been traded away. A structural test cannot see any of this, which is
why it is measured.
test_layout_responsive.py pinned the listener's name and follows the
rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8oRqEHhnKUr1NfmTVdcyL
|
| |
|
|
|
|
|
|
|
|
|
| |
b6f3139 renamed `fit` to `fitAndPin` in chat-app.js and rebound the
resize / orientationchange / visualViewport listeners to it, but
test_the_panel_refits_when_the_viewport_changes still grepped for the old
name and failed. `fit` itself still exists (fitAndPin wraps it), so
test_the_measurement_survives_a_scrolled_page was unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
GroupPage's 6620-line app.js carried Chat and Files wedged in directly, with
no way to add another group-level app without touching the shell itself. It
is now app.js (routing, non-group pages) plus nine focused files — apps.js
(the registry), chat-app.js, files-app.js, video-player.js, group-page.js
(the shell), group-settings.js, hub-client.js, icon.js and file-utils.js —
with docs/apps.md as the checklist for adding one (Videos/Music/Photos are
sketched there, not built).
Node side gained the matching enablement mechanism, mirroring
member_upload exactly: a roster setting, a signed apps_enabled op enforced
by _has_admin_authority, exposed in the handshake ack. Operators toggle
applications per group from Settings, which also gained a small reorder:
Invite, Pairing, Applications, Shared directories, Uploads, danger zone,
Your devices, Members.
Two bugs surfaced during the split, both missing an import across the new
file boundary and invisible to node --check or a module-load probe since
they only throw when the code path actually runs:
- group-page.js called onRefreshAuth on a stale-token handshake rejection,
but app.js never imported refreshAccessToken from hub-client.js — so a
brand new member (including a group's own creator) hit "Not a member of
this group" and the retry silently failed, throwing before it could
refresh the token.
- chat-app.js called getLocale() for message timestamps without importing
it from i18n.js. Opening Chat on a group with real messages threw mid-
render; uncaught, that appears to wedge Preact's render scheduler, so
every button on the page stopped responding until reload.
Caught the second class of bug with a proper no-undef audit across all
split files (a temporarily installed ESLint 9, since the system one is too
old to parse this codebase's syntax) rather than trusting grep. 827 tests
pass; 6 new ones cover the apps_enabled policy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016SF6RKNBKg9qejmoMJ9ybA
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Reported: on mobile you see only the right-hand edge of the panel, without the
content. Measured, before anything was changed:
320 px viewport -> panel at -138..192, 138 px off the left
360 px -> -98..232
412 px -> -46..284
The panel is 330 px wide and anchored to the right edge of its button — but
that button is not at the right edge of the screen, since the bell and the user
menu come after it. What falls off is the left-hand side, which is where the
file names are, so what stayed on screen was a strip of progress bars belonging
to nothing.
Narrowing it would not have helped: the overflow comes from where the right
edge is pinned, not from the width. Below the existing 768 px breakpoint the
panel is anchored to the viewport instead, full width on a phone and capped at
420 px on a tablet, where stretching two filenames across 750 px would be
silly. Desktop keeps its 330 px against the button.
The interesting part is how it was found. The responsive tests read numbers out
of the stylesheet and said, in their own docstring, that a layout could not be
measured because the suite had no browser. It has one now — Chrome, from the
video work — so layout_probe.py renders the real stylesheet at a given width and
returns rectangles. `width: 330px` was never the thing worth asserting on.
An iframe carries the viewport, because a headless window will not go below
about 500 px, and one browser measures every width: launching one per test put
three minutes on the suite against twenty-six seconds for all of them. Checked
that the new tests fail with the rule removed — three of them do — and that
they pass with it back.
|
|
|
Chat opens on the newest hundred messages, loads fifty older on demand with the
reading position anchored — the distance from the *bottom*, since everything
above the viewport just grew — and follows new messages only when the reader was
already at the end. Day separators, sender grouping, an unread marker, and a
jump-to-latest pill. Messages are keyed by id: index keys plus prepending makes
Preact reuse the wrong bubbles.
A presence dot per group in the sidebar, three states, each backed by something:
the hub's registry, or a connection this browser made or failed to make. Never
colour alone — red and green are the pair colour-blind readers cannot separate —
so each dot carries a title and an aria-label.
Profile is split out of Settings: identity, node link, pinned node identities and
account deletion. Mixing them put an irreversible button two scrolls under a
theme picker. The create-group page loses its centred 520 px card, which left
190 px of margin either side, and its two button panels become a radio group —
a button conveys no chosen state to a screen reader, and side by side they read
as two independent actions rather than one either/or.
The Files toolbar shows its actions as icon buttons the moment Select is on,
disabled when they do not apply rather than appearing and vanishing. On a phone
the right-hand group could not wrap and ran 130 px off the screen.
Streamed downloads no longer freeze after one chunk. `registration.active` says
a worker exists, not that this page is controlled by it — and an uncontrolled
page's requests never reach its fetch handler, so the worker took the stream and
was never asked for it, leaving `writer.write()` waiting on backpressure that
would never lift. The page now requires control and the worker confirms it
actually served the request before the sink is trusted.
Fixed on the way: `setActionsOpen` outlived the state it belonged to and threw
on every Files action; the chat scrollbar stopped short of the bottom; the
owner's row sat lower than the rest; About showed a version hardcoded two
releases ago.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|