Improved the webui pages for phone - #121
Conversation
Added Board posts display Added for Network to list last Chats
Fixed css issues
|
@defnax I feel there are some other |
|
I hate this scss, i get very often destroyed corrupted this |
Sure take a look when you get chance. So, we need to be careful with the |
|
But i commited all changes what i see in my git app. |
* 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
|
@zelfroster new review? |
|
@defnax I was working on streamlining some things and adding instructions, so the styles don't get messed up each time. Let me push the changes in a bit, we can merge that, and then after you update this PR accordingly, will review it. |
|
yes this issue with styles i get headdache, then i lose motivation when its destroyed and losing time |
|
Defnax please rebase this pr/121 on master so that it gets a chance to be reviewed and merged, and I can work on it. |
@jolavillette @defnax What's the plan for webui rn btw? I mean broadly do we already have planned features to implement or things to improve? Since, we are going to use AI most probably to improve things here, it would be better to add an AGENTS.md with some specific instructions here too, else the webui product as a whole would have inconsistent looking UI/UX. |
|
As far as I am concerned:
May I respecfully suggest that you also use AI to make a first pass on PRs, so that you can focus on what is worth your human time? IMO that's the way everything should go now in the whole RetroShare project. |
Makes sense 👍🏼
Yeah, that's a great suggestion, I was thinking of doing that too. @csoler I was wondering if we can get an AI tool reviewer to automatically review PRs so the author's can fix the obvious issues etc, and then the actual reviewer can take a better look to save everyone's time and accelerate the work on webui maybe. |
|
This one is a good AI reviewer to use if we can get it for our project - https://www.greptile.com/open-source |
|
of course if that can be helpful. What we should avoid absolutely is the AI merging PRs by itself or even being able to modify the code in the repository. |
I had no time now my code broken i has some conflicts on my code with master |
|
I think after this merged i will make a break with webui |
OK I will rebase it on master |
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.
…iods 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.
refreshGpgDetails() re-injects every remembered friend the core does not return, and nothing ever removed one: a short-invite peer that never validates, or one the user deletes, came back on the next refresh and survived a browser restart, with no way to get rid of it from the web UI. A remembered friend is a placeholder for the seconds the core needs to catch up with an add that has just returned, so it now carries the time it was made and is dropped once that window has passed. Removing a friend drops it immediately, before the refresh that would have restored it -- and that refresh now waits for removeFriend to answer instead of racing it. The store is also keyed per node: it lives in localStorage, which is shared by every RetroShare profile reached from the same browser, and their friend lists have nothing to do with each other.
The Ok button of the web help confirmation opens the documentation in another tab and left the dialog standing behind it, with nothing left to ask. It closes now, like the one in the copy confirmation. Every modal that gets a phone layout drops its padding from 1.5rem to 1rem, but .close-btn is positioned against the box rather than the content, so it stayed 1.5rem from the edge and lined up with nothing. Corrected once in the modal layout instead of in each of the five pages that override the padding.
Uses nearly the full phone viewport with safe spacing. Keeps the folder list independently scrollable. Keeps Add New and Edit buttons visible at the bottom. Converts folder rows into labeled mobile cards. Replaces the stray 0 with “No shared folders yet.” Ensures nested dialogs appear above ShareManager.
…r-121 Remembered friends must expire, and go when the friend goes
…r-121 Stop four request storms, and refresh the channel list at all
…r-121 Web help dialog stays open, and the modal close button drifts on phones
|
@zelfroster I leave the merging up to you. |
Once hashing starts there is no way out: Cancel is disabled, the backdrop click is guarded by the same flag, and the overlay covers the whole viewport, nav included. The only exits are the browser's Back button and a reload -- and Back does not even stop the poll, since the setTimeout chain runs off module state that nothing resets, so it keeps asking once a second for the rest of the session and the dialog is waiting again on the way back. That would be harmless if the wait always ended, but the core drops a file it failed to hash without a word: ftExtraList only records successes, so the file never enters mHashedList and ExtraFileStatus() answers "not ready" for ever. hashExtraFile() has already refused missing paths and directories by then, so this is the file that exists but cannot be read -- permissions, a share unmounted, an I/O error. The red button therefore stays enabled and reads Stop while hashing, the backdrop closes again, and leaving the view stops the poll: it writes its file link into that view's textarea, so once the view is gone the answer has nowhere to land anyway. The poll also backs off from one second to ten, since a large file legitimately takes minutes and each request opens a new connection.
…-for-121 The attach dialog must be escapable while it hashes
…r answered Select a contact, Profile details, Start chat: the tunnel goes green, what you type leaves and is echoed, and the answers never appear. They do arrive -- the core posts an RsChatMessageEvent for every incoming message and the handler runs -- they are just written into a list nobody displays. Giving each peer its own session pointed State.chatMessages, the array the view renders, at session.messages. loadChatMessages() then kept doing State.chatMessages = data.msgs: that reassignment does not update the session, it detaches from it. From that answer on there are two arrays -- the visible one holding the history, your echo and the status notices, and the session one where the event handler pushes everything the peer says. The `else if` fallback of the handler cannot save it either, since the session is always found first. It fires every time: the JSON API answers msgs: [] rather than nothing, so an empty history detaches just as surely as a full one. The session array is now the only one: it is mutated in place and State.chatMessages is never reassigned, only re-pointed at it. Messages carry a key -- direction, send time and text -- shared by the three sources, so the same message coming from the history and from the live event is stored once, and the status notices no longer need their manual text scan. Two windows were losing messages besides. The core emits its "starting distant chat" notice from inside initiateDistantChatConnexion, before the caller knows the pid, and rswebui buffers every chat event it cannot deliver; the tab also unregisters its handler when the user walks away. Both are covered by draining that buffer -- when the chat opens, when the tab mounts, and when the tunnel turns secure -- and by adopting the visible conversation for a message that names the pid the view is already showing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e knows Clicking Chats sets the counter climbing for minutes. It is not counting conversations, it is watching a probe walk the whole identity list: the tab re-runs preloadAllChatHistory(), which asks /rsHistory/getMessages once per known identity, and the badge is recomputed from the answers as they land. On a node that has seen twenty thousand identities that is twenty thousand requests, four at a time, against a JSON API that answers one at a time and closes the connection after each -- and it starts over at every click, on top of the three calls the layout and the sidebar already make when the page opens. Neither half of the probe can find anything. Distant chat history is not stored under the peer's GXS id. The core keys it by the tunnel id, sha1 of the two GXS ids sorted and truncated to sixteen bytes (p3GxsTunnelService::makeGxsTunnelId), so the query only ever answers an empty list and the previews appear solely for chats opened in this session. There is no API to derive that id and no crypto.subtle outside a secure context, the web UI being served over plain HTTP, so people_util now computes the digest and the probe asks for the tunnels of our own identities against that peer. Private chat history is keyed by a location, not by a person: the query passed the PGP id where an RsPeerId is expected. It is half the length, the core cannot parse it, so it builds a null id -- which is the key of the public broadcast history -- and prints a stack trace for every request. 1714 of them in one log here, over 979 distinct PGP keys. It now asks per location of the friend behind the identity, which is where the messages actually are. So the probe covers the peers a conversation can exist with -- contacts, peers already in the map, identities belonging to our friends -- instead of every identity ever seen, and it will not run twice within thirty seconds however many times the tab is clicked. The badge and the list read chatHistoryMap directly: it only ever holds peers with a real message, so the sweep of the identity list on every redraw bought nothing, and identity details are fetched for the handful of peers actually listed. The History dialog was asking the same two impossible questions and now asks the right ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The right pane keeps its last tab in State.activeTab, and that state is global rather than per contact. So the list item handler ends on "if the chat tab is the one showing, connect" -- which is never true on a fresh page, and always true afterwards. Once a conversation has been opened, every later click in the contact list requests a GXS tunnel toward whoever was just selected: a network action the other side sees, from a click that only meant "show me this profile". Selecting somebody now shows their profile, and the tunnel waits for the Chat Conversation tab. The three places that do mean it -- the tab itself, the Start Chat button, the Chats list and its context menu -- are untouched. The mirror case was broken the other way round. "Start private chat" from a chat room calls setSelectedId(id, 'chat'), which preselects the chat tab on a page that is not mounted yet; nothing ever opened the tunnel there, so the pane sat on its Connecting spinner for good. That intent is explicit, so it is now remembered and honoured once the own identities are loaded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…wered rsJsonApiRequest hands the callback the HTTP status, not the answer, and the button read that as "the tunnel is closed": any reply at all, including one that says nothing was closed, ended the conversation on screen. The core does report it, in retval, so that is what is read now, and the ended card says which of the two happened -- closed on request, or already gone before the click. The status poll had the same deafness and is what let it happen. Once a tunnel is gone from the core -- died of inaction, closed by the peer -- getDistantChatStatus answers false, and the poll only ever looked at the branch where it answers true. So the last known status stayed on screen for good: a conversation whose tunnel had been dead for a while kept its green dot and its "You can talk", and Leave Chat then had, quite correctly, nothing to close. Two consecutive false answers now end the conversation, with a line saying so, which is the state the user was in. Needs the libretroshare side to be meaningful: fix/distant-chat-close-keeps-contact makes closeDistantChatConnexion report whether it closed anything, and drop the distant chat contact it used to leave behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Edit dialog opens on an empty name field -- it is never filled from the identity it was given -- and Save sends whatever stands in it, so an edit meant for the picture alone renames the identity to nothing. The picture goes anyway: updateIdentity(id, name, avatar, pseudonimous, pgpPassword) takes the avatar as a mandatory parameter, p3IdService assigns it with no condition attached (group.mImage = avatar), and the request carried none. A missing JSON member is not "keep what you have", it is an empty image. The line that would have sent it has been sitting there commented out. So the name is filled in from the identity, an empty one is refused, and the current avatar is always sent back -- with a file picker to replace it, since the dialog now has to carry it either way. Deleting had the opposite problem: it announced success without reading retval, and refreshed nothing. watchOwnIds() is not a watcher, it listens for the event refreshOwnIds() emits, so an identity deleted from this page stayed in the list until something else happened to reload it. Two more in the same file: a refused passphrase left the Create button disabled on "Creating…" for good, since only .catch() cleared the flag and a refusal is a perfectly valid answer; and the passphrase dialog asked for a "passpharse" then reported "Successfully created identity." after an update. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tten
"Type" tested `mFlags === 14` against a bitfield. 14 is PGP_LINKED | PGP_KNOWN |
IS_OWN_ID, so the only identities that ever matched were our own signed ones: a
friend's signed identity carries 6, or 7 with the contact bit, and was labelled
"Anonymous ID". The bit to look at is RS_IDENTITY_FLAGS_PGP_LINKED, 0x2.
"Usage Statistics" mapped mServiceId over 1..8, but that field is an
RsServiceType -- 0x0211 for the identities, 0x0215 for the forums, 0x0217 for
the channels, 0x0012 for the chat. No case could match, so every line of the
panel came out as "Unknown (533)". The usage codes next to it are right, they
really do run 0 to 21; it is the service that was being read from another table.
The two timestamps read `.xint64` straight out of the field instead of going
through get64Num, which exists for exactly that: a 64 bit value arrives as
{xint64, xstr64} and large ones carry the string alone, where the pane then
shows "Invalid Date".
Details were also fetched once and kept for the whole session -- deliberately,
it is what makes the lists cheap -- which froze the reputation, the usage record
and the avatar of the profile being *looked at*. That one is now asked again
after a minute, over the top of what is displayed rather than by clearing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three things in the People chat pane, all about what happens between two conversations. The text being typed lived in a single page-wide field that nothing cleared when the selected peer changed, while the per-peer session carried an inputMsg nobody ever read. A message written to one contact was still in the box when the next conversation opened, one Enter away from the wrong recipient. Every write now goes through the session, and opening a conversation restores its own draft. Changing the identity we talk as opened a second tunnel and abandoned the first: the id is sha1(sorted(own || peer)), so another identity means another tunnel, and nothing closed the one being left. It is closed now, before the new one is asked for. The paperclip pasted the path itself into the message -- the peer received "/home/me/x.iso" and nothing else. It now publishes the file as an extra file and sends the retroshare:// link the core answers with, which is what the chat page does; the hashing runs behind a banner that says what is being hashed and can stop it, since a file that exists but cannot be read is dropped silently by ftExtraList and would otherwise be waited on for ever. And the failed-send alert blamed a packet size limit that does not exist: getMaxMessageSecuritySize() answers 0, unlimited, for distant chat, and the core slices anything past 15000 characters and reassembles it. The picture compression stays -- several megabytes of base64 through a turtle tunnel is still a bad idea -- but says why it is there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…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
zelfroster
left a comment
There was a problem hiding this comment.
This PR has grown quite big, when we have reached a certain point we should plan to review and merge it and do further work in separate PRs.
|
big work is done next time then only small prs |
Uh oh!
There was an error while loading. Please reload this page.