Skip to content

Dark mode - #126

Open
defnax wants to merge 145 commits into
RetroShare:masterfrom
defnax:dark-mode
Open

Dark mode#126
defnax wants to merge 145 commits into
RetroShare:masterfrom
defnax:dark-mode

Conversation

@defnax

@defnax defnax commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

defnax and others added 30 commits July 26, 2026 20:50
Added Board posts display
Added for Network to list last Chats
* corrected some place holder text
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.
Fixed channels thumbnail layout issue
Added same comments design from boards to channels
Fixed layout issues for myfiles, friendfiles & search
Brings the branch up to date with master so RetroShare#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.
The `@media (max-width: 768px)` block opened for the People page was never
closed, so `sass` refused the whole stylesheet:

    Error: expected "}".
    app/scss/pages/_people.scss 427:2  @forward

This is why the styles kept "getting destroyed": `npm run build` could not
produce styles.css at all, and the file had to be maintained by hand.

The closing brace goes before `.people-context-menu`, not at the end of the
file: that rule is written at column 0 under its own header comment, so it
is meant to be a top level rule. Left inside the media query it would only
style the right-click menu below 768px, which is where it is least likely
to be used.

styles.css is regenerated by `npm run build`, which is why it comes back
from 25200 hand-assembled lines to the 7 compressed lines the sass pipeline
emits.
build.ps1 (and the matching hunk of build.sh, dropped while rebasing)
worked around the broken stylesheet by concatenating raw .scss sources onto
the end of the generated styles.css:

    $extraCss += Get-Content "$src\app\scss\pages\_board.scss"

That ships SCSS to the browser. Nesting, `&`, `@use` and variables are not
CSS, so everything after the first nested block is dropped by the parser,
which made the result look randomly broken and fed the same manual fixing
loop.

With the missing brace fixed in the previous commit, `sass` compiles the
whole tree again and the workaround has nothing left to work around.
build.ps1 was also unreachable: make-src/build.js dispatches win32 to
build.bat, never to a PowerShell script.
master gained an eslint config in 35c5c17 and the code added here predates
it, so `npm run lint` fails on this branch. This commit is the mechanical
part only, produced by `eslint app --fix` with no hand editing:
object-shorthand, quote style, trailing whitespace, prefer-const.

Twenty-two problems are left because they need a decision rather than a
rewrite rule, the two worth looking at first being real:

  boards/board_view.js:499,503  'forumId' is not defined  (no-undef)

`renderComment` calls `util.voteForPost(forumId, ...)` for the up and down
vote buttons, but no `forumId` exists in that scope - it looks like it came
over from the forums code. Voting on a board comment therefore throws
ReferenceError and silently does nothing.

The other twenty are unused bindings left behind by the refactors
(`get64Num`, `loadLobbyDetails`, `Message`, `SubscribedLobbies`,
`PublicLobbies`, `LayoutSingle` in chat.js, `closePopup` in
board_kanban.js, `bsubscribed`/`bposts`/`createDate`/`lastActivity` in
boards_util.js, `displaycomment` in channel_view.js) plus a few dead
assignments. `npm run lint` lists them all.

Drop this commit if you would rather keep the diff to your own changes.
Merge master into improvements_v2, and fix the scss that breaks the build
trying to restore tooltip issue on chat rooms
Improved idenity selector on boards/channels loading
… item

Opening the channels list made the web UI unusable and left channel pages
blank, with the console filling up with

    getChannelContent  net::ERR_INSUFFICIENT_RESOURCES
    [RS] Retroshare-jsonapi not available.

The second line is misleading: it is what rswebui prints for status 0, i.e.
a request that never left the browser. The service is fine, the tab is out
of sockets.

updatedisplaychannels() asked getContentSummaries for the ids of everything
in a channel - posts, comments *and* votes, each one a separate GXS item -
then fired one getChannelContent per id:

    res2.body.summaries.map(async (content) => {
      await updatecontent(content, keyid);   // contentsIds: [content.mMsgId]
    });

`.map` with an async callback awaits nothing, so all of them start at once.
And ChannelSummary calls updatedisplaychannels from its oninit, once per
channel of the list, so the whole subscription set does this simultaneously.
A few dozen posts is enough: it is the number of items across every
subscribed channel that counts, and each one costs a socket.

getChannelContent already takes a set:

    rsgxschannels.h:385
    virtual bool getChannelContent(const RsGxsGroupId& channelId,
                                   const std::set<RsGxsMessageId>& contentsIds, ...)

so the ids are now sent in batches of 200, sequentially per channel, with a
redraw between batches so posts appear as they arrive. A channel of 2000
items goes from 2000 requests to 10.

Splitting a batched answer needs all three lists walked rather than the
first non-empty one, so the storing moves into storePost / storeComment /
storeVote. They key off each item's own mMeta instead of the summary it was
requested from, which is the same value but no longer assumes a one to one
mapping between request and result.

The two responses are also checked before use: rsJsonApiRequest resolves to
undefined when the request fails, which the previous code dereferenced
straight away - the exact case that was happening here.
…t exist

A channel or board with no logo falls back to `data/streaming.png`:

    src: cimage.mData.base64 === '' ? 'data/streaming.png' : ...

That file is in neither webui-src/assets nor the served tree, so the request
comes back 404 and the browser draws a broken image icon.

Both sites now render no <img> when there is nothing to show, and guard the
whole chain rather than comparing to '': mImage is absent rather than empty
on some groups, and `undefined.base64` throws during the view, which in
mithril takes the whole render down. The board side already had that guard,
it just kept aiming at the missing file.

These were the last two references to it - the post thumbnails on this
branch already go through channelThumbnailSrc / ChannelFallbackThumbnail.
Every own-identity lookup logs two errors in the console:

    POST http://localhost:9092/rsIdentity/getOwnIds  404 (Not Found)
    [RS] HTTP error: 404 Not Found

getOwnIds is the deprecated one and carries no @jsonapi annotation, so the
core does not expose it at all:

    rsidentity.h:636
    RS_DEPRECATED_FOR("getOwnSignedIds getOwnPseudonimousIds")
    virtual bool getOwnIds(std::list<RsGxsId>& ownIds, bool only_signed_ids)

getOwnSignedIds and getOwnPseudonimousIds are the annotated ones
(rsidentity.h:458 and :466), so their union *is* the complete list rather
than a fallback for older cores.

people_util therefore drops the attempt and goes straight to the two calls
it was already making underneath. The catch that was meant to swallow the
failure never ran either: rsJsonApiRequest does not throw on a non-200, it
resolves with the status, so the code reached the union by way of
`Array.isArray(undefined)` being false. It worked by accident.

boards_util had the same call with no fallback at all, which made
voteForPost report "No identity found to vote." whenever no voterId was
passed. It now goes through people_util.ownIds(), so the endpoints and
their 30s cache live in one place.
Chat messages are carried as HTML. renderChatMessage() strips the tags but
never decodes the entities left behind, and the result goes into a mithril
text node, so they reach the screen verbatim.

The visible case is a plain space: RetroShare sends the message as HTML as
soon as the composer holds any formatting, and QTextDocument::toHtml()
turns leading and repeated spaces into &nbsp;, which the web UI then
displays as that literal string. &amp;, &lt; and &gt; were affected the
same way.

The three call sites that turned HTML into text carried the same sequence
of replacements, so they now share htmlToText(). Decoding goes through a
textarea rather than a div on purpose: the content model of a textarea is
plain text, so no part of a message can be parsed into an element. Nothing
is trusted as markup at any point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stop calling the deprecated /rsIdentity/getOwnIds (404 on every lookup)
Fix the request storm that makes channels unusable
jolavillette and others added 30 commits August 18, 2026 10:06
…eir choosing

renderChatMessage turns any <img src="..."> found in a received message into a
real <img>, and the browser then goes and gets it. On a distant chat -- carried
by a turtle tunnel precisely so that neither end knows where the other is -- that
hands the reader's address to whatever host the sender picked, and tells them
the moment the message was read. Pictures embedded by RetroShare travel as data:
URIs; anything else is now shown as the text it is rather than fetched.

The rest of this commit is the People tab paying for its lists.

"All Users" is every identity the node has ever seen -- this profile's gxsid_db
is 35 MB -- and the sidebar rendered all of them, firing one getIdDetails per row
from inside its own view. The list is capped at 200 rows with a line saying how
many are left, and the search narrows it.

jdenticon redraws an avatar's SVG on every call, and that call sat in the view of
every avatar on screen: once per avatar per redraw, and a redraw happens on every
answer of every poll. Same id and same size, same drawing -- memoised.

The history browser is mounted with the page and draws nothing until asked for,
so its oninit was the moment a *conversation* opened, not the moment somebody
wanted the history: opening a chat ran "give me every message ever stored" for a
panel nobody had asked for. It loads when it opens now, which also fixes the
chat page, where the panel was loaded once at mount and never again. Its overlay
follows the three others onto dvh.

Two small ones: adding or removing a contact from the context menu reloaded the
identity summaries alone, while isContact is read from rs.userList.userMap, which
only loadUsers() refreshes -- so the list kept showing the old state; and about
250 lines of unreachable code are gone, people_own_contacts.js entirely, the
"Own Identities" widget of people_ownids.js, the second search box of
people_util.js and the isSearched marking those two shared, which contactlist()
and sortUsers() still wrote from inside a view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
People tab: distant chat repairs and a full audit pass
The Refresh button was guarded by a `loading` flag set once at oninit and never
raised again, so it only ever disabled itself before the first answer. After
that every click fired two more requests, on top of the five second interval,
with nothing preventing two runs from overlapping -- and on a phone they do
overlap, since each answer closes its connection and the core serves them one at
a time. `load()` now refuses to start while one is running and always clears the
flag, including on the paths that throw.

Friend names came from a second collection of the friend list -- getFriendList
plus one getPeerDetails per friend, all at once -- run exactly once, at oninit:
a friend added afterwards stayed `Peer 1a2b3c4d…` until the page was reloaded.
network_data already does that work and keeps it in NetworkData.gpgDetails for
the whole session, so the page reads that and asks for a refresh at most once a
minute rather than never.

`formatBytes` was a second implementation of `rs.formatBytes`, printing MiB
where the statusbar, the chat and the file lists print MB for the same number.

The pie segments are rotated back to twelve o'clock, where a pie chart is read
from; the totals in the middle stay upright.
The address fallback deduces "local or external" from the RFC1918 ranges, but
the core already says which is which: getPeerDetails appends its own marker to
every ipAddressList entry, "    123 sec loc" or "    123 sec ext"
(p3peers.cc). Guessing instead gets two cases wrong -- a peer behind CGNAT
(100.64/10) or a double NAT has a private looking external address, and a
loopback entry is not external at all -- where the marker is always right.

The parser was also IPv4 only, which misses the very case the fallback exists
for: GetRetroshareInvite() clears extAddr and moves the address into
ipAddressList when it is IPv6, because the certificate format only carries IPv4
numbers, so a peer added by short invite over IPv6 has an empty external address
and an entry the regex could not read. RsUrl wraps IPv6 hosts in brackets and
escapes the % of a link-local scope id as %25; both are handled, and the entry
with no marker at all -- the one that invite path produces -- is external by
construction.
Statistics page: overlapping polls, a second friend collection, a second formatBytes
…r-121

Peer addresses: read the core's loc/ext marker, and handle IPv6
…like one

The overlay carries role=dialog and aria-modal=true, but its Escape handler sits
on the overlay itself and therefore only sees what bubbles through it. The close
button is focused on open, so Escape works -- until the first tap on the picture,
which moves the focus to <body>: from then on the only way out is the close
button or the Back button.

The handler moves to the document, in the capture phase, and is removed when the
preview closes. Tab is caught there too: aria-modal promises the rest of the page
is inert, and without it the focus walks straight out of the preview into the
message list behind it. Closing also puts the focus back on whatever opened the
preview rather than dropping it on <body>.
markReadLocally() clears the whole 0xf0 nibble, but RS_MSG_TRASH is 0x20 and
lives inside it (rsmail.h). Opening a message from the Trash therefore cleared
its trash flag as well, and the row showed as an ordinary read mail until the
next load. Only the two unread bits are cleared now.

The badge itself is read from the navigation view, so unreadCount() ran a full
pass over the inbox on every redraw -- once for the rail and twice more for the
bottom bar, which asks for the value twice per item. The count is kept instead,
recomputed when the inbox changes: after a load, and after a message is marked
read.
- Fixed to get more space for the text field
The message pane scrolled to the bottom from its onupdate, unconditionally. That
hook runs on every redraw of the chat hub -- an incoming message, a poll answer,
a keystroke in the composer -- so scrolling up to read anything older was undone
a few tens of milliseconds later, every time.

It now follows the reader: the pane stays pinned while it is already at the
bottom, stops as soon as it is scrolled away from it, and pins again when it
comes back. Sending a message still jumps down whatever the state, and switching
room opens on its last message, since the pane is reused rather than recreated
and its oncreate does not run again.

The scroll handler sets event.redraw = false: mithril redraws after every
handler by default, and this one fires continuously while the pane is dragged.
A room opens on its last 20 messages and that was all there ever was: reaching
the top of the pane loaded nothing, so anything older was only reachable through
the separate history browser.

Scrolling near the top now asks for a larger slice. p3HistoryMgr::getMessages
takes a count and nothing else -- no cursor, no "before this message" -- and
always answers with the newest ones, so the only way to reach older text is to
ask for more and let addMessages() drop what is already held. It re-sends what
we have, which is the price of that API; the slice is small, and the core keeps
ten days at most anyway.

An answer shorter than the count asked for means there is nothing older left,
and the pane stops asking. The opening slice stays at 20: every room opening
pays for it, and on a phone each request is a fresh connection on a core that
answers one at a time.

Older messages are inserted above what is on screen, which would push the
conversation down by their height; that height goes back into scrollTop, so the
reader does not move while a slice lands.
…for-121

Image preview: Escape, Tab and focus restore
Mail badge: reading a message must not untrash it, and stop recounting per redraw
…for-121

Chat rooms: read further back by scrolling up
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants