The scope predicate of the channel list background task was entirely commented
out, so it returned undefined: setBackgroundTask stopped after the first
interval and the list was loaded once, never refreshed while the page stayed
open. It now tests the route like the boards one does.
The three group lists polled at three different periods: forums every 5 s,
boards every 30 s, channels never. Each poll fetches the whole summaries list,
and on a phone it costs a fresh TCP handshake on a server that answers one
request at a time. All three now share the boards period, named per page.
Every JSON API answer carries `Connection: close` and the server runs its
service on a single thread, so each request costs a full TCP handshake and they
are answered one at a time. Over loopback a round trip is 0.1 ms and none of
this shows; over WiFi or 4G it is 20 to 80 ms and the three patterns below turn
into seconds of blank screen.
Chat room list: one getChatLobbyInfo per subscribed room, and nothing was
painted until the last answer arrived -- then only did the public room list get
asked for. Paint each room as it lands and start the public list immediately.
Forum post bodies: loadPostContent() is called from the view, and the body stays
null for the whole round trip, so every redraw fired the same getForumContent
again -- and each of the other posts' answers causes a redraw. An open thread
multiplied one request per post into one per post per redraw. Guarded by an
in-flight set, kept on failure so an unavailable post is asked for once per
visit rather than forever.
Chat history preload: two getMessages per known identity, hundreds of them at
once, filling the browser's six sockets and the single service thread while the
user waits for something else. Run four at a time. The duplicated response
handling of the two branches is now one function.
Three defects in the new thread composer.
The 199 000 limit of a GXS message is a *byte* count, but the composer compares
it against String.length, which counts UTF-16 units: an accented letter is two
bytes for one unit, an emoji four bytes for two. With an emoji picker one click
away in that very toolbar, the counter can report room left on a message the
core will refuse. It now measures UTF-8 through TextEncoder, and says bytes
rather than characters.
postBody() was called five times per render -- twice for the class, twice for
the counter text, once for the disabled state -- and it re-escapes the message
and re-joins every inline image, each of which is up to 175 KB of base64. That
ran on every global redraw, which the statusbar triggers continuously, while the
user is typing. It is now built once per pass.
pollFileHash() re-arms itself every 500 ms for up to a minute. Closing the
composer left it running: it kept polling and redrawing a component that is no
longer mounted. onremove now stops it.
- Added a default Forum thumbnail to the forum details card.
- Added a default Board thumbnail for boards without an uploaded image.
- Added a default Channel thumbnail when no image is set.
Opening a forum threw, and every redraw after it threw again:
TypeError: Cannot read properties of undefined (reading 'view')
at initComponent
NotFoundError: Failed to execute 'removeChild' on 'Node'
userList.userMap holds {name, isContact} objects (rswebui.js:300 and :317),
not strings. userList.username() is the accessor that unwraps them:
const name = typeof entry === 'object' ? entry.name : entry;
but several views read the map directly and handed the raw object to mithril:
fauthor = rs.userList.userMap[forumDetails.author]; // forum_view.js
...
m('p', m('b', 'Admin: '), fauthor)
Vnode.normalize returns anything that is `typeof === 'object'` untouched, and
createNode sends every non-string tag to createComponent, so the object was
treated as a component and initComponent dereferenced `vnode.tag.view` on
undefined. Once a redraw throws inside createNodes the vdom no longer matches
the DOM, which is where the removeChild storm comes from: the statusbar, the
identity bulk fetch and the forum load all redraw, and all of them fail.
It only bites when the author is already in the identity cache, which is why it
looks intermittent: the render that breaks is the one triggered by fetchBulk's
redraw, when the entry flips from missing to object.
Same direct read, same crash, in the channel view (header author, and the
comment author cell) and in the board view. The identity selectors called
.toLocaleString() on the entry, which does not throw but prints
"[object Object]" as the option label.
All of them now go through username(), which returns the name when it is known
and the raw id otherwise, and which queues the missing ids for the next bulk
fetch on the way.
Cause: the thread view created a second full-height .widget inside the page’s existing .widget. The nested height and overflow rules caused long posts to extend into a clipped area.
Changes:
- Removed the nested full-height widget
The main Forums widget is now the single vertical scroll area
- Added safe wrapping for long text
- Added horizontal scrolling for wide code blocks and tables
- Constrained images, videos, and embeds to the available width
Brings the branch up to date with master so #121 can be merged again.
Eight files needed a decision. The branch predates 41111cb ("restore WebUI
styles in SCSS sources"), which recovered into the SCSS a number of rules
that until then existed only in the compiled styles.css, so most conflicts
are that recovery meeting the changes made here. Where both sides describe
the same element the branch's version is kept; where master's rule is a
positioning context the branch has no equivalent for, master's is kept:
* _chat.scss - master's attach-modal, emoji-picker, rightbar context menu
and create-lobby-button rules are kept alongside the compact-message
styles added here. Taking either side whole would have dropped about
450 lines. .chat-own-profile-card, .chat-hub-rightbar and
.chat-hub-rightbar .user keep `position: relative`, because
.chat-create-lobby-btn and .rightbar-context-menu are still declared
`position: absolute` further down and would otherwise escape to the
initial containing block. For .user-tooltip the branch's `position:
fixed` rework wins, and master's `.chat-hub-rightbar .user-tooltip
{ left: -275px }` offset is dropped since it would fight a fixed
element.
* _people.scss - master's restored .friends-list-container context menu
is kept; the pane rules it also restored are shadowed by the branch's
own !important versions and were dropped as dead code.
* boards_util.js - the revived updateContent() and the in-flight dedup in
updateDisplayBoards() are kept. Master's side of the first two hunks
was comment reformatting only.
* board_view.js - the rewrite here is kept whole. Master's only change
since the fork point was `let reader` -> `const reader`, in code the
rewrite replaced.
* chat.js - the modals moved out one nesting level here; that structure
is kept.
* chat_state.js - `require('people/people_util')` is not restored. It is
unused, which is why 35c5c17 removed it.
* build.sh - master's version. The change here concatenated raw .scss
onto the generated CSS; see the following commits.
* styles.css - regenerated, see the following commits.
Added Navbar for webui on phones
for Boards and Channels:
* Validate API responses before processing.
* Keep rendering/navigation functional if a response is incomplete.
* Log a clear warning instead of throwing an uncaught error.
* Use independent sorted arrays, avoiding mutation of the master list.