Skip to content

feat: log viewer, downloads, settings and document viewer as separate windows on a shared native shell - #3444

Merged
jeanfbrito merged 41 commits into
devfrom
feat/log-viewer-revamp
Aug 13, 2026
Merged

feat: log viewer, downloads, settings and document viewer as separate windows on a shared native shell#3444
jeanfbrito merged 41 commits into
devfrom
feat/log-viewer-revamp

Conversation

@rodrigok

@rodrigok rodrigok commented Aug 8, 2026

Copy link
Copy Markdown
Member

What changed

The log viewer, downloads, settings and the document viewer each open as their own window, built on one shared window chrome instead of four separate implementations.

Previously all four lived inside the main window: settings and downloads as full-window takeovers, the log viewer as a bare page, the document viewer as an overlay on top of the server pane. Pulling them out means the reader keeps their workspace visible while reading logs, a PDF, or changing a setting, and each window can be sized and placed independently.

Shared chrome

src/ui/windowChrome/ holds what the windows have in common — toolbar, sidebar filter rows and sections, nav rows, status bar, day headers, text buttons, surface resolution, caption buttons and the transparency hook. A window is now mostly its own content plus a sidebar.

The toolbar replaces the native title bar on both macOS and Windows: macOS keeps its traffic lights floating over it, Windows hides the caption entirely and the toolbar draws its own minimise/maximise/close from the main window's existing glyphs. Linux keeps its native frame. The buttons act on whichever window sent the request, so one registration serves all four.

Transparency follows the existing setting and applies live, without a restart. transparent cannot be toggled after a window is created, so — as the root window already does — these windows are always transparent with a vibrancy material on macOS, and the setting decides only whether the renderer paints an opaque surface over it.

All four windows remember their position and size, and the log viewer, downloads and settings reopen at launch if they were open at shutdown. Bounds come from getNormalBounds() so maximising does not overwrite the size to restore to, are debounced because move/resize fire continuously while dragging, and are dropped when they no longer overlap any display — a window restored onto an unplugged monitor is a window the reader cannot reach.

Log viewer

  • Filters moved to a left sidebar: level, scope and server, multi-select, with a select-all control per section.
  • A distribution timeline above the list, with click-and-drag to select a time range. Drawn directly rather than pulling in a chart library — it is one bar per bucket over data already in memory.
  • The list paginates as it scrolls instead of rendering every entry.
  • Sticky day headers, so the date stays visible while scrolling.

Downloads

  • Grouped by day, with the row actions always visible rather than appearing on hover — a download's controls are the point of the list.
  • File names open the file in the OS; a preview button opens Quick Look on macOS. Both resolve the path in the main process from its own state, so the renderer never passes a path across IPC.
  • File type icons are drawn inline from palette neutrals, replacing a fixed white page image that read as a bright block in dark mode. Monochrome by choice — the icon identifies a row, the file name is what the reader is scanning for.
  • A placeholder stands in for the filters while there is nothing to filter, instead of a search field over an empty column.

Settings

  • Sections are a registry rather than one long page.
  • Appearance is new, taking the theme and layout settings that were scattered through General. Theme and layout are picked from thumbnails, which use literal colours rather than palette tokens: they are the one place in the app that must not follow the current theme, or all three options would render identically while sitting in dark mode.
  • Telephony and Video calls split apart — they were grouped only by both being "calls".
  • Advanced replaces the old About dialog and absorbs the Developer section: version, update channel, hardware acceleration, error reports and the logging switches — the things a reader reaches for when something is wrong.
  • Checking for updates leads General as a single row, the automatic check and the manual one sharing a field. Version and copyright sit at the foot of the sidebar, small and unlabelled.
  • Search matches individual settings, not just section names, and names the matching settings in the row so it is clear why a section is still listed. Fuzzy subsequence matching applies only to short labels — over prose it matched almost anything (vibr hit "Video calls"), so longer text falls back to substring.
  • Certificates merge trusted and untrusted into one list with the state shown per row and a filter field, replacing two lists that had to be compared by eye.

On macOS the About menu item now opens the system About panel; every Mac app has that item in the same place and its contents come from the bundle. Windows and Linux have no such convention, so they get no About item at all.

Document viewer

PDFs and markdown open in their own window rather than an overlay over the server pane. Every entry point already dispatched SERVER_DOCUMENT_VIEWER_OPEN_URL, so the window listens for that one action and both the page's open request and the intercepted markdown download redirect at once; no caller changed. The document still renders in a webview on the originating server's session, which is what lets an authenticated URL resolve at all. One window, reused — a second document replaces the first.

Both formats get a download button. The bytes are read on the workspace's own session, so an authenticated document saves as the signed-in user rather than as an anonymous request; a blob the server page created is read back through that page's web contents, since a blob URL resolves nowhere else.

Markdown also gets a source toggle, for reading a file as written rather than rendered. The text is already fetched to render it, so switching costs no round trip.

These two are the only viewers in the app — PDF and markdown are the formats the workspace preload can open, and both now live here.

Fixes found along the way

  • PDF link interception never ran. PdfContent announced its webview to the main process on did-attach, but getWebContentsId() throws until the guest document exists, so the call threw every time and the announcement never arrived. It now announces on dom-ready.
  • Settings spacing was uneven. Three fields set their own block margins, which beat FieldGroup's rhythm — the PDF size limit sat 16px below its neighbour while everything else sat at 24. Worse, TelephonyGlobalShortcut never accepted the className that FieldGroup passes down, so its rows had no gap at all. Seven components that hand-rolled Field markup now use the shared SettingField / ToggleField wrappers. Measured on the built window: every gap within a group is 24px, every gap across a divider 49px.
  • A time-fragile test. DownloadsIndicator reads Date.now() twice and a download counts as unseen only between the two; the test left that window under a millisecond wide, so it passed on the macOS runners and failed on Linux and Windows. Date.now() is stubbed instead. Master landed the same fix independently in the meantime, and its version is what this branch now carries.
  • An invalid colour token. The markdown viewer set bg='surface', which is not in the palette — Fuselage logged "invalid color: surface" on every render and painted nothing. The window's card already carries the background, so the prop is gone.

Testing

yarn lint, npx tsc --noEmit and yarn test all pass (169 suites, 1907 tests).

Unit coverage: log parsing, timeline bucketing, pagination convergence, download grouping, file labels, fuzzy matching, the settings search index, the section registry, per-platform title-bar options, window-open reducers and saved-bounds validation.

Rendering was verified by loading the built bundles in Electron with stubbed IPC and seeded state — measuring computed surface colours, geometry, caption-button placement, field spacing and search results in both themes, and forcing process.platform to check the Windows chrome — rather than eyeballing screenshots.

Not covered by automated tests, and worth a look during review:

  • vibrancy and traffic-light alignment on macOS; caption buttons on a real Windows build
  • Quick Look preview, and reopening windows at launch
  • position restore surviving a real quit and relaunch
  • a blob: document in the viewer window, both displaying it and downloading it: Chromium registers those per origin and partition, so a webview on the same partition should resolve them, but that is worth confirming against a real workspace

Notes

SettingsView, DownloadsManagerView, CertificatesManager and the in-pane DocumentViewer are now unreachable. They are left in place to keep the diff to the new windows; removing them is a follow-up.

The dialog.about.* translation keys stayed put now that the dialog is gone — renaming them to settings.* would orphan every existing translation in the other locales.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added dedicated Downloads, Settings, Log Viewer, and Document Viewer windows.
    • Search, filter, group, manage, preview, and save downloads.
    • View Markdown and PDF documents, including raw Markdown mode.
    • Redesigned log viewer with filtering, timelines, paging, highlighting, copying, and live status.
    • Added searchable settings, certificate management, theme previews, and update controls.
    • Secondary windows restore size and position with native controls and theme support.
  • Bug Fixes

    • Improved download opening, previewing, and PDF attachment handling.
    • Added native macOS About panel details.
    • Improved keyboard navigation and copy confirmations.

rodrigok and others added 2 commits August 7, 2026 21:06
Rebuilds the log viewer window around a left filters sidebar and a unified
toolbar, and makes the window honour the transparency setting.

Window:
- Samples isTransparentWindowEnabled at creation and applies transparent +
  sidebar vibrancy on macOS. `transparent` cannot be toggled afterwards, so
  the sampled value is passed to the renderer in the page query rather than
  fetched over IPC — an async read would flash an opaque surface first.
- Uses the toolbar as the title bar on macOS (hiddenInset) so the window
  shows one header instead of a native title bar stacked on an in-app one.
  Traffic light geometry is derived, not guessed, so toolbar content clears
  the buttons.

Filters:
- Levels, contexts and servers are faceted checkbox lists with counts. Each
  count reflects the other filters, so a count is never unreachable.
- Context tags are parsed into a list instead of a whitespace-joined string,
  which also lets contexts be discovered from the file rather than hardcoded.
- Selections persist; "empty means everything" so a stored selection stays
  valid when new levels or tags appear.

List:
- Entries are paged in as the reader scrolls instead of being capped by an
  entry-limit control, which read as a filter but was pagination. Copy and
  Save act on every match, not just the rendered page.
- Day headers are virtual list group headers, so the date stays readable at
  any scroll position.
- Multi-line entries fold to their first line with an expand toggle, search
  matches are highlighted, and each row can be copied on its own.
- Metadata tags sit above the message so every message shares one left edge
  and one width.

Adds specs for the parser, the facet toggle and the paging advance. The paging
advance in particular must settle once everything is rendered: the virtual
list keeps firing endReached while the last row is in view, so an unbounded
increment re-renders forever and wedges the renderer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a histogram above the log list showing how the matching entries are
spread over the file's time span, oldest on the left, each bar stacked by
level so a burst of errors stays visible inside an otherwise busy period.

Dragging across the plot selects a time range and filters the list to it; a
plain click selects the single slice under the pointer, and the range clears
from the chart, from Clear Filters, or with Escape mid-drag.

The chart is built from the matches of every filter *except* the time range.
Feeding it the range-filtered set would collapse the chart onto the selection
and leave no way back to the rest of the span. Facet counts do include the
range, so each control still reports what selecting it would yield.

Drag listeners are bound imperatively on mousedown rather than in an effect
keyed on drag state: an effect only runs after the next render, so a drag fast
enough to finish inside one task lost its own mouseup and stayed stuck — which
is exactly what a synthesized-event test caught.

No chart library. The part worth owning is bucketing log entries by time and
level, which is here and covered by tests; the rendering is flex boxes using
the existing palette tokens, and a library would have added a second styling
system plus hundreds of KB to a secondary window for one histogram.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9cda67b4-9a3c-48e2-8943-e6a0c3f364bb

📥 Commits

Reviewing files that changed from the base of the PR and between 66c9314 and 4666c44.

📒 Files selected for processing (3)
  • src/downloadsWindow/DownloadRow.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/settingsWindow/tests/SettingsWindow.spec.tsx
  • src/downloadsWindow/DownloadRow.tsx
  • src/settingsWindow/tests/SettingsSidebar.spec.tsx
📜 Recent review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: build (ubuntu-latest, linux)
  • GitHub Check: build (windows-latest, windows)
  • GitHub Check: check (ubuntu-latest)

Walkthrough

This change adds standalone document viewer, downloads, settings, and log viewer windows. It adds shared window chrome, persisted window state and bounds, IPC handlers, renderer bundles, redesigned interfaces, search and filtering utilities, document saving, and updated integrations.

Changes

Secondary windows and shared foundation

Layer / File(s) Summary
Window state, IPC, and startup
src/app/*, src/ipc/channels.ts, src/store/*, src/ui/actions.ts, src/ui/reducers/*, src/ui/main/*, src/main.ts
Secondary-window state and bounds are persisted, restored, validated, and synchronized through Redux and IPC. Startup registers and restores the new windows.
Shared window chrome
src/ui/windowChrome/*
Reusable toolbars, controls, filters, status components, appearance helpers, styles, and hooks support themed secondary windows.
Renderer shells and bundles
rollup.config.mjs, src/public/*-window.html, src/*Window/*-window.tsx
Renderer entry points and HTML shells initialize Redux, i18n, system-theme tracking, transparency, and React mounting.

Document viewer

Layer / File(s) Summary
Document viewer flow
src/documentViewerWindow/*, src/ui/components/ServersView/*
The document viewer loads authenticated Markdown and PDF content, supports raw Markdown display, saves documents, restricts navigation, and replaces the previous in-pane viewer flow.

Downloads

Layer / File(s) Summary
Downloads window and file actions
src/downloadsWindow/*, src/downloads/main.ts, src/downloads/main.spec.ts
The downloads window supports search, facet filtering, day grouping, status controls, file actions, Quick Look preview, and persisted window state.
Downloads integration
src/ui/components/TopBar/*, src/i18n/en.i18n.json
The top-bar downloads panel reuses DownloadRow, updates labels, and opens the full downloads window.

Log viewer

Layer / File(s) Summary
Log viewer redesign
src/logViewerWindow/*
The log viewer adds structured parsing, facet filters, timeline selection, pagination, grouped rendering, expandable entries, copy actions, toolbar controls, and status reporting.
Log viewer validation and localization
src/logViewerWindow/__tests__/*, src/i18n/en.i18n.json
Tests cover parsing, pagination, timeline calculations, and localized viewer controls.

Settings

Layer / File(s) Summary
Settings window and sections
src/settingsWindow/*
The settings window provides searchable section navigation, fuzzy matching, certificate management, platform-specific sections, update controls, and persisted section selection.
Settings presentation updates
src/ui/components/SettingsView/features/*
Theme and navigation choices use window thumbnails. Several settings fields use shared field components and spacing helpers.

About and existing UI integration

Layer / File(s) Summary
Native About panel and existing UI updates
src/app/main/app.ts, src/ui/main/menuBar.ts, src/ui/components/AboutDialog/*, src/ui/components/Shell/*, src/ui/components/TabBar/*, src/ui/reducers/currentView.ts
macOS uses Electron’s native About panel. The previous About dialog and retired root-window views are removed. Shared toolbar height is used by the tab strip.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: moving the log viewer, downloads, settings, and document viewer into separate windows on a shared native shell.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

rodrigok and others added 2 commits August 8, 2026 11:37
Shell, matching the main window's concept: the log area is an inset rounded
card with a hairline and a soft shadow, and the toolbar, sidebar and status bar
carry no fill of their own.

The panel colour lives on the window root, not on each bar. Painting the bars
individually left the card's 4px gutter showing a different surface, so the
card had a halo and its rounded corners read as a cut-out. Now body, root and
every bar resolve to one continuous colour and only the card paints.

That colour also has to be *recessed* or the corners look like a hole punched
in a lighter surface. `surface-tint` gives that in the light palette — grey
behind a white card — but inverts in the dark one, where it is lighter than
`surface-light`, so the dark panel is mixed down from the card colour instead.

Transparency now applies without reopening the window. `transparent` cannot be
toggled after creation, so — exactly as the root window does — the window is
always created transparent with a vibrancy material on macOS, and the setting
only decides whether the renderer paints an opaque surface over it. The initial
value still arrives in the page query so the first paint matches; changes are
pushed to the open window afterwards.

The window also reopens at launch when it was open at shutdown, showing itself
without taking focus from the main window. Only a deliberate close records
`false`: the `closed` handler fires on quit as well, so it checks a
`before-quit` flag first, or quitting would erase the state it is meant to
restore.

The sidebar toggle is a filters glyph instead of a burger, and ghost instead of
`pressed` — the filled state read as a heavy block wedged against the traffic
lights, and the sidebar's own presence already shows whether it is open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Downloads leaves the main window's view stack and becomes a separate window,
built on the same shell the log viewer uses.

The shell is now a shared module (src/ui/windowChrome) rather than something
each window reimplements: window surfaces and the inset card, the macOS
title-bar toolbar with its drag region and traffic-light inset, the sticky day
header, the status bar and its items, the sidebar filter rows and sections,
the facet toggle logic, the link-weight button and the transparency hook. The
log viewer was moved onto it, which is most of the churn here.

Opaque surfaces now come straight from the main window: `surface-neutral`
behind the content and `surface-light` for the content itself, so all three
windows read as the same app. The card carries no hairline — the shadow alone
lifts it — and the toolbar, sidebar, card gutter and status bar paint nothing,
leaving one continuous panel.

Downloads window:
- Every existing entry point already dispatches the same action, so the main
  process listens for it once rather than editing each call site; the root
  window keeps whatever view it was on.
- Rows are list rows, not cards: icon, name, and one muted line of server, size
  and — while a transfer is live — its rate and time left. A finished
  download's name opens the file, and on macOS there is Quick Look beside it;
  both resolve the path from main's own state by id, so a renderer cannot ask
  for an arbitrary file to be opened.
- Grouped under sticky day headings, filtered by faceted server/type/status
  lists with counts, and reopened at launch when it was open at shutdown.

Both windows drop their sidebar-hide toggle and their shared "clear filters"
footer; each facet section resets itself instead, and the destructive action
sits in the status bar beside the count it affects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rodrigok rodrigok changed the title feat: revamp Log Viewer with sidebar filters, window transparency and a distribution timeline feat: separate Log Viewer and Downloads windows on a shared native window shell Aug 9, 2026
@rodrigok rodrigok closed this Aug 9, 2026
@rodrigok rodrigok reopened this Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

macOS installer download

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

rodrigok and others added 2 commits August 9, 2026 20:52
The old FileIcon drew a fixed white page, which read as a bright block on
the dark window. This one is inline SVG, so its fill, outline, fold and
label all come from palette neutrals at low alpha — one drawing that works
on both themes.

Deliberately monochrome. The icon identifies a row; the file name is what
the reader is looking for, and colour-coding by type competed with it.

Row hover moves to a shared LIST_ROW_CLASS, so the downloads list and the
window chrome's filter rows highlight from the same rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Settings becomes the third window on the shared chrome, alongside the log
viewer and downloads: same toolbar, sidebar, nav rows and surface handling,
so transparency and the card treatment follow the other two for free.

Sections are a registry rather than one long page. Appearance is new and
takes the theme and layout settings that were scattered across General;
telephony and video calls split apart, since they were only ever grouped by
both being "calls". The theme and layout options are now picked from
thumbnails, drawn with literal colours rather than palette tokens — these
are the one place in the app that must not follow the current theme, or all
three options would render identically.

Search matches settings, not just section names, and names the matching
settings in the row so it is clear why a section is still listed. Fuzzy
subsequence matching is applied only to short labels: over prose it matched
almost anything ("vibr" hit "Video calls"), so longer text falls back to
substring.

Certificates merge trusted and untrusted into one list with the state shown
per row and a filter field, instead of two lists that had to be compared.

All three windows now remember where they were left. Bounds are saved from
getNormalBounds() so maximising does not overwrite the size to restore to,
debounced because move and resize fire continuously while dragging, and
dropped when they no longer overlap any display — a window restored onto an
unplugged monitor is a window the reader cannot reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rodrigok rodrigok changed the title feat: separate Log Viewer and Downloads windows on a shared native window shell feat: log viewer, downloads and settings as separate windows on a shared native shell Aug 9, 2026
rodrigok and others added 5 commits August 10, 2026 09:55
…tions

The secondary windows kept their native Windows caption while also drawing
an in-app toolbar, so each one showed two headers stacked. They now hide the
title bar as the main window already does there, and the toolbar draws the
caption buttons into its trailing edge.

The glyphs and button styling come from the main window's own controls, so
this is not a second set that drifts from it; only the wiring differs. The
main window's buttons dispatch redux actions bound to that one window, while
these ask the main process to act on whichever window sent the request — one
registration serving all three. Maximised state is pushed back per window,
so the glyph shows restore even when the change came from a double click on
the toolbar rather than from the buttons.

The toolbar reserves the buttons' width at its leading edge too, so the
title stays centred in the window rather than in what is left of it.

Settings opens wider. A full row of theme thumbnails did not fit the old
680px minimum and wrapped to a second row, which reads as a layout accident
rather than a choice. The new minimum is derived from the option's real
width — 178px, not the thumbnail's 168px, because the selection ring is
drawn whether or not an option is selected — through a metrics module the
ring, the grid gap and the thumbnail all share, so the three cannot drift.
Measured against the built window: 876px wraps, 878px does not, and the
minimum leaves the Windows scrollbar its 10px, which the macOS overlay
scrollbar does not take.

The downloads sidebar shows a placeholder while there is nothing to filter,
instead of a search field over an empty column that reads as a rendering
failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The About dialog is gone. On macOS the menu item now opens the system About
panel — every Mac app has that item in the same place, and its contents come
from the bundle, so a hand-built dialog was a worse version of something the
OS already provides. Windows and Linux have no such convention, so they get
no About item at all.

What the dialog actually held moves into settings. Update channel and the
logging switches that had their own Developer section now sit in a new
Advanced section together with hardware acceleration and error reports —
the things a reader reaches for when something is wrong. The section list no
longer needs a developer-only flag: Advanced hides its own developer parts.

Checking for updates leads General as a single row: the automatic check and
the manual one are the same decision from two angles, so the toggle and the
button share a field rather than sitting apart. Version and copyright move
to the foot of the sidebar, small and unlabelled — worth being able to find
and copy, not worth a row of their own.

Spacing is one rhythm now, 24px between settings and a hairline where a
group genuinely ends. Three fields had been setting their own block margins,
which is why the PDF size limit sat closer to its neighbour than anything
else did, and four more hand-rolled the Field markup instead of using the
shared wrappers. The telephony shortcut was the worst case: it dropped the
className FieldGroup passes down, and with it the group's spacing entirely,
so its rows had no gap at all. Measured against the built window: every gap
within a group is now 24px, every gap across a divider 49px.

Sidebar rows all carry the same text and icon colour — the fill behind the
selected one already says which is selected, and dimming the rest made the
list read as mostly disabled. Keyboard focus draws a highlight ring instead
of borrowing that fill, which had left two rows claiming to be current.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four conflicts, all of them both sides adding to the same place: the new
downloads simulation listener next to this branch's open-file and preview
handlers, the downloads-percentage reducer next to the window-open ones, its
action next to the secondary-window actions, and master's DownloadsIndicator
mock where this branch had removed the About dialog's. Both sides kept in
each case.

One thing needed wiring rather than merging: master added Downloads
percentage to GeneralTab, which this branch no longer renders, so the
setting moves to the settings window's General section and its search index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The indicator reads Date.now() twice — once for the mount-time seenAt, once
when the button is clicked — and a download counts as unseen only while its
endTime sits between the two. The test set endTime to mountTime + 1, so that
window was a fraction of a millisecond wide: it passed when mount and render
landed in the same tick and failed when they did not. On the Linux and
Windows runners they did not, and this is the test failing on master.

Date.now() is stubbed instead, mount and click ten seconds apart, so the
download is unambiguously unseen at mount and seen after the click.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The viewer was an overlay inside the server pane, which meant reading a
document and reading the conversation it came from were mutually exclusive.
It becomes the fourth window on the shared chrome instead, so the workspace
stays visible behind it and the window can be sized and placed on its own.

Every entry point already dispatched SERVER_DOCUMENT_VIEWER_OPEN_URL — the
page asking to open a PDF, the main process intercepting a markdown download
— so the window listens for that one action and both paths redirect at once;
no caller changed. The document still renders in a webview on the
originating server's session, which is what lets an authenticated URL
resolve at all.

One window, reused: a second document replaces the first rather than piling
up near-identical windows.

Fixes a viewer bug found on the way. PdfContent announced its webview to the
main process on `did-attach`, but getWebContentsId() throws until the guest
document exists, so the call threw every time and the announcement never
arrived — leaving the main process unable to intercept link clicks inside a
PDF and route them to the browser. It now announces on `dom-ready`, guarded
so navigation inside the viewer does not register the handler twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rodrigok rodrigok changed the title feat: log viewer, downloads and settings as separate windows on a shared native shell feat: log viewer, downloads, settings and document viewer as separate windows on a shared native shell Aug 10, 2026
rodrigok and others added 9 commits August 10, 2026 13:46
Both viewers get a download button. The bytes are read on the workspace's own
session, so an authenticated document saves as the signed-in user rather than
as an anonymous request; a blob the server page created is read back through
that page's web contents, since a blob URL resolves nowhere else.

Markdown gets a source toggle. The text is already fetched to render it, so
switching between rendered and source costs no round trip.

Also drops a `bg='surface'` from the markdown viewer. There is no such palette
token — Fuselage logged "invalid color: surface" on every render and painted
nothing — and the window's card already carries the background.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One conflict, in the downloads indicator's seen test: master pinned Date.now()
for that test independently, the same fix this branch had made. Master's
version is taken verbatim so the file stops diverging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The secondary windows' toolbars were 52px while the main window's tab strip
is 40px, so the windows did not line up as the same app. They read the height
from one constant now rather than each carrying its own number, and the macOS
traffic lights stay centred in it because their position is derived from it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One conflict, in the workspace-switcher setting: master dropped the Linux
menu-bar coupling that disabled the tabs option, while this branch had
replaced the radio buttons with thumbnails. The thumbnails stay and the
coupling goes, so the option is offered on every platform as master intends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clicking the visible checkbox did nothing: Fuselage draws it as a label
wrapping a real input, so the click reached the row twice — once on its way
up from the box, once from the click the label forwards to the input — and
the filter toggled straight back to where it started. Clicking the row's
label worked, which is why it read as an unreliable checkbox rather than a
broken one.

The checkbox was meant to be inert, via pointer-events, but that never
reached the element Fuselage puts the handler on. It reports its own change
now and keeps its clicks to itself; the row handles everywhere else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unchecking the last option in a facet silently ticked every box again, so
the list could never be narrowed to nothing from the sidebar — the one thing
a reader tries when they want to see what a filter is actually doing.

The cause was one value meaning two things: an empty selection stood for
"untouched, so everything", which the facets need in order to keep taking in
servers and file types that only show up later. Untouched is `null` now and
an empty list means what it says, so all three states are expressible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A window created on macOS while the app is in full screen was given full
screen itself, so opening Downloads or Settings from a full-screen workspace
replaced it rather than appearing alongside it.

`fullscreenable: false` is what refuses that, and it refuses it outright:
even an explicit setFullScreen(true) leaves the window windowed. Maximise,
minimise and resize are untouched.

Several richer approaches were tried first and are not here for good reason.
Toggling the workspace collection behaviour around show() left the main
window in a full-screen state it could not be brought out of, and making the
secondary windows children of the main window turned it black — both worse
than the problem. This changes one constructor option and can do neither.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel had its own item component, so a download looked one way in the
downloads window and another in the panel a click away. It renders the
window's row now and that component is gone.

Making the row portable took removing its one tie to the downloads window: a
`surfaces` prop used for a single divider colour that is the same palette
token in every theme. The server name is a prop instead — worth a line in the
window, which filters by it, and only crowding in a panel listing a handful
of downloads from the session at hand.

Progress no longer changes a row's height. It was an 8px bar in the flow, so
every row below jumped 12px the moment a transfer started; it is a line along
the row's own bottom edge now. Drawn directly rather than with Fuselage's
ProgressBar, whose animated shine is an absolutely positioned pseudo-element
with no containing block of its own — it escaped the bar and swept a white
band across the whole list.

Every row's last action is a cross now, so the button nearest the edge does
not move as a download progresses. They remain different actions: cancel
while a transfer is live, remove from the list once it is over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test opened the panel with userEvent and then searched the whole tree
with a regex. Both got slower when the panel started rendering the full
download row, and on the Windows runner the pair crossed the 5s limit.

It clicks with fireEvent and asserts against the panel's text instead, which
tests the same thing: a transfer that has not moved any bytes shows no size.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reopening a secondary window only called focus(), which does not
deminiaturize on macOS or restore on Windows. A shared helper now
restores, shows, and focuses in every reuse branch.
Await the save-document result and show a dismissible danger Callout
on failure (cancellation stays silent). Queue DOCUMENT_CHANNEL sends
behind did-finish-load with last-request-wins so rapid opens cannot
be lost, and key the content webview on partition:url so a same-URL
document from another workspace remounts under its own session.
isFacetSelected now takes the facet universe: values a facet cannot
name (like normal downloads' 'All' status) always match, so narrowing
the status facet no longer hides every normal download. toggleFacet
sanitizes persisted selections against the universe so stale values
cannot collapse the selection back to everything.
The check-for-updates result effect now waits for the check to actually
start before acting on its settle, instead of consuming the request on
stale state. When an update is found the settings window closes so the
root window's update panel is visible. Section auto-selection during
search is a non-persisted override; only explicit clicks persist.
persistValues used a leading-edge throttle that silently discarded
writes within one second, losing pre-quit window-state changes. It now
coalesces to a trailing write of the latest values, and before-quit
flushes anything still pending.
Load errors now show a danger Callout with retry instead of the
"adjust filters" empty state. Saving acknowledges success on the
toolbar button, stays silent on cancel, and shows a dismissible
Callout on failure.
The plot already advertised role=slider. Arrow/Home/End now move
the selected bucket, Shift extends the range, Escape clears it,
and aria-value* reports the active bucket. Mouse drag and the
clear button reset the keyboard anchor.
Card radius uses --rcx-border-radius-large and the shadow uses
--rcx-color-shadow-elevation-1 (Tile's elevation-1 chain) so
the shared card follows theme and high-contrast instead of
hardcoded rgba and platform px.
The section list advertised a tablist without the tabs contract.
Arrow/Home/End now move the selected section, only the current tab
is in tab order, and the content panel is linked with aria-controls.
Selected labels use fontScale p2b so state is not color-only.
Opaque hover and selected fills now use surface-hover/selected.
TextButton keeps an unfilled look but gains a 24px hit target and
a focus ring. Copy and save ticks are announced on a polite live
region, and save uses its own label instead of "Copied".
The live dot is a StatusBullet, level stripes use bullet/badge
fills at 1px, and Display rows are ToggleSwitches so they no
longer look like data filters. Row-action fade respects
prefers-reduced-motion.
Clear All and certificate remove now ask through the existing
Electron dialog pattern before they run. The downloads progress
fill uses ProgressBar's info token, and the width animation
stops under prefers-reduced-motion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/logViewerWindow/logViewerWindow.tsx (2)

185-187: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset timeRange when the log source changes.

Line 185 states that the range belongs to the current file. handleOpenLogFile and handleOpenDefaultLog change the source but retain timeRange. If the ranges do not overlap, the new file shows no entries.

Clear timeRange before changing the selected log source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/logViewerWindow/logViewerWindow.tsx` around lines 185 - 187, Reset
timeRange to null in both handleOpenLogFile and handleOpenDefaultLog before
updating the selected log source, ensuring each newly opened log starts without
the previous file’s range filter.

257-338: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Discard responses from older load requests.

loadLogs can overlap when the user changes files or requests a refresh. An older read-logs request can resolve last and overwrite logEntries, fileInfo, and currentLogFile for the newer source.

Use a monotonically increasing request identifier. Guard every response state update, including setIsLoading(false), against the latest request identifier. parseGenerationRef only creates distinct entry IDs. It does not prevent stale responses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/logViewerWindow/logViewerWindow.tsx` around lines 257 - 338, The loadLogs
callback must discard results from overlapping requests by introducing a
monotonically increasing request identifier. Capture the identifier at the start
of each loadLogs invocation and guard every response-dependent state update,
including setLogEntries, setExpandedEntryIds, setCurrentLogFile, setFileInfo,
setLoadError, and setIsLoading(false), so only the latest request can update
state; do not use parseGenerationRef for this purpose.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/main/persistence.ts`:
- Around line 76-86: Update the elapsed-time comparison in persistValues to use
an inclusive boundary, so elapsed values equal to THROTTLE_INTERVAL_MS clear
pendingValues and call writeNow synchronously. Add a test covering elapsed ===
THROTTLE_INTERVAL_MS and verify it does not schedule a trailing save.

In `@src/documentViewerWindow/DocumentViewerWindow.tsx`:
- Around line 62-82: Update handleDownload to catch rejected
document-viewer-window/save-document invocations and setSaveError to
t('documentViewer.downloadError') when rejection occurs, while preserving the
existing cancellation and unsuccessful-result handling.

In `@src/logViewerWindow/logViewerWindow.tsx`:
- Around line 1066-1080: Prevent stale log entries from rendering when loadError
is present: update the refresh failure handling around handleRefresh to clear
logEntries, or gate the GroupedVirtuoso and timeline rendering on the absence of
loadError. Preserve the retry callout and ensure failed refreshes do not display
previous entries.

---

Outside diff comments:
In `@src/logViewerWindow/logViewerWindow.tsx`:
- Around line 185-187: Reset timeRange to null in both handleOpenLogFile and
handleOpenDefaultLog before updating the selected log source, ensuring each
newly opened log starts without the previous file’s range filter.
- Around line 257-338: The loadLogs callback must discard results from
overlapping requests by introducing a monotonically increasing request
identifier. Capture the identifier at the start of each loadLogs invocation and
guard every response-dependent state update, including setLogEntries,
setExpandedEntryIds, setCurrentLogFile, setFileInfo, setLoadError, and
setIsLoading(false), so only the latest request can update state; do not use
parseGenerationRef for this purpose.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c939c6eb-d867-4785-ad57-11c8c56bb3d2

📥 Commits

Reviewing files that changed from the base of the PR and between 09fa0a3 and 1103968.

📒 Files selected for processing (43)
  • src/app/main/persistence.main.spec.ts
  • src/app/main/persistence.ts
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/documentViewerWindow/ipc.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/downloadsWindow/DownloadRow.tsx
  • src/downloadsWindow/DownloadsWindow.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/downloadsWindow/ipc.ts
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/i18n/en.i18n.json
  • src/ipc/channels.ts
  • src/logViewerWindow/LogEntry.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogViewerToolbar.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/logViewerWindow/appearance.ts
  • src/logViewerWindow/ipc.ts
  • src/logViewerWindow/logViewerWindow.tsx
  • src/logViewerWindow/styles.tsx
  • src/main.ts
  • src/settingsWindow/SettingsSidebar.tsx
  • src/settingsWindow/SettingsWindow.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/ipc.ts
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.tsx
  • src/ui/main/secondaryWindowFocus.ts
  • src/ui/windowChrome/NavRow.tsx
  • src/ui/windowChrome/TextButton.tsx
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/ui/windowChrome/__tests__/filters.spec.ts
  • src/ui/windowChrome/appearance.ts
  • src/ui/windowChrome/filters.ts
  • src/ui/windowChrome/styles.tsx
  • src/ui/windowChrome/useCopiedFeedback.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/logViewerWindow/LogViewerToolbar.tsx
  • src/main.ts
  • src/ipc/channels.ts
  • src/ui/windowChrome/filters.ts
  • src/ui/windowChrome/tests/filters.spec.ts
  • src/downloadsWindow/DownloadsWindow.tsx
  • src/downloadsWindow/DownloadRow.tsx
  • src/ui/windowChrome/styles.tsx
  • src/documentViewerWindow/ipc.ts
  • src/settingsWindow/SettingsWindow.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.tsx
  • src/ui/windowChrome/NavRow.tsx
  • src/ui/windowChrome/appearance.ts
  • src/logViewerWindow/ipc.ts
  • src/logViewerWindow/appearance.ts
  • src/settingsWindow/ipc.ts
  • src/settingsWindow/sections/CertificateRow.tsx
  • src/i18n/en.i18n.json
  • src/logViewerWindow/LogEntry.tsx
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: build (macos-latest, mac)
  • GitHub Check: build (ubuntu-latest, linux)
  • GitHub Check: check (macos-latest)
  • GitHub Check: check (windows-latest)
  • GitHub Check: check (ubuntu-latest)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for new code unless explicitly told otherwise.
Use Fuselage components from @rocket.chat/fuselage for UI work unless the design requires something Fuselage does not provide.
Check Theme.d.ts for valid color tokens before using Fuselage colors.
Verify library props, APIs, and tokens against official docs or local .d.ts files instead of assuming.
Use React functional components with hooks.
Redux actions follow FSA shape.
Use camelCase for file names and PascalCase for components.
Prefer clear names over unnecessary comments.
Prefer editing existing files over creating new abstractions unless the new abstraction removes real complexity or matches an existing pattern.

**/*.{ts,tsx}: Use TypeScript for all new code unless explicitly told otherwise.
Use Fuselage components for all UI work; create custom components only when Fuselage lacks the required functionality.
Import Fuselage components from @rocket.chat/fuselage.
Use only valid color tokens documented by Theme.d.ts.
Use optional chaining with fallbacks for platform-specific APIs, especially Linux-only process APIs such as process.getuid(), getgid(), geteuid(), and getegid().
Use TypeScript strict mode.
Redux actions must follow the Flux Standard Action pattern.
Use camelCase for file names and PascalCase for component names.
Avoid unnecessary comments; prefer self-documenting code through clear naming.
Do not commit or push without explicit user permission.
Verify library APIs, props, tokens, and types against official documentation and .d.ts files instead of assuming they are valid.

Files:

  • src/ui/main/secondaryWindowFocus.ts
  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/ui/windowChrome/TextButton.tsx
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/ui/windowChrome/useCopiedFeedback.ts
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
  • src/downloadsWindow/ipc.ts
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
  • src/app/main/persistence.ts
  • src/logViewerWindow/styles.tsx
  • src/logViewerWindow/logViewerWindow.tsx
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Renderer specs use *.spec.ts / *.spec.tsx.

Files:

  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
src/*/*/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Renderer specs must live in a Jest-matched nested path, such as src/<module>/<subdir>/*.spec.ts(x); flat src/<module>/*.spec.ts files are not discovered by the current testMatch.

Files:

  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
src/**/*.{spec.ts,spec.tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Renderer test files should be placed in nested module paths such as src/<module>/<subdir>/*.spec.ts(x) so Jest discovers them.

Files:

  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use React functional components with hooks.

Files:

  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/ui/windowChrome/TextButton.tsx
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
  • src/logViewerWindow/styles.tsx
  • src/logViewerWindow/logViewerWindow.tsx
**/*.main.spec.ts

📄 CodeRabbit inference engine (AGENTS.md)

Main-process specs use *.main.spec.ts.

Use *.main.spec.ts for main process tests.

Files:

  • src/settingsWindow/main/ipc.main.spec.ts
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/app/main/persistence.main.spec.ts
**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.spec.ts for renderer process tests.

Files:

  • src/settingsWindow/main/ipc.main.spec.ts
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/app/main/persistence.main.spec.ts
🧠 Learnings (24)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: qa/AGENTS.md:0-0
Timestamp: 2026-07-09T13:51:14.404Z
Learning: Applies to qa/** : Classify changed Desktop surfaces by user-visible risk, including Electron main process, protocol handlers, OS default handlers, settings UI, menus, modals, packaging/installers, startup, shortcuts, workspace routing, i18n, and layout.
📚 Learning: 2026-06-26T18:14:15.729Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3358
File: src/ui/components/SettingsView/features/E2ePdfPreviewSizeLimit.tsx:47-55
Timestamp: 2026-06-26T18:14:15.729Z
Learning: In `src/ui/components/SettingsView/features/SettingField.tsx` for the Rocket.Chat Electron App settings panel, full-width selects and inputs are intentional by design: the UXDQA Figma spec calls for controls to stretch to the form column width in the stacked label/description layout, and the macOS panel was verified 1:1 against that spec. Do not flag full-width numeric inputs such as `src/ui/components/SettingsView/features/E2ePdfPreviewSizeLimit.tsx` as layout regressions in this panel.

Applied to files:

  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/settingsWindow/SettingsSidebar.tsx
📚 Learning: 2026-08-12T14:11:40.244Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat.Electron PR: 3444
File: src/settingsWindow/sections/GeneralSection.tsx:16-16
Timestamp: 2026-08-12T14:11:40.244Z
Learning: In Rocket.Chat.Electron renderer TypeScript/TSX files running with nodeIntegration enabled, direct access to process.platform and process.mas is intentional; do not require optional-chaining fallbacks for these properties. Optional-chaining safeguards should apply to Linux-only APIs such as process.getuid(), process.getgid(), process.geteuid(), and process.getegid(). Adding fallbacks for process.platform or process.mas can silently select an incorrect UI layout and conceal a renderer configuration error.

Applied to files:

  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/ui/windowChrome/TextButton.tsx
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
  • src/logViewerWindow/styles.tsx
  • src/logViewerWindow/logViewerWindow.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.spec.ts : Use `*.spec.ts` for renderer process tests.

Applied to files:

  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.main.spec.ts : Use `*.main.spec.ts` for main process tests.

Applied to files:

  • src/settingsWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/app/main/persistence.main.spec.ts
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.main.spec.ts : Main-process specs use `*.main.spec.ts`.

Applied to files:

  • src/settingsWindow/main/ipc.main.spec.ts
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
📚 Learning: 2026-07-09T13:51:14.404Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: qa/AGENTS.md:0-0
Timestamp: 2026-07-09T13:51:14.404Z
Learning: Applies to qa/** : Classify changed Desktop surfaces by user-visible risk, including Electron main process, protocol handlers, OS default handlers, settings UI, menus, modals, packaging/installers, startup, shortcuts, workspace routing, i18n, and layout.

Applied to files:

  • src/settingsWindow/main/ipc.main.spec.ts
📚 Learning: 2026-07-09T13:51:14.404Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: qa/AGENTS.md:0-0
Timestamp: 2026-07-09T13:51:14.404Z
Learning: Applies to qa/**/scripts/*.mjs : If a script mutates OS state, put the mutation behind an explicit flag and document cleanup in the matching flow.

Applied to files:

  • src/settingsWindow/main/ipc.main.spec.ts
  • src/downloadsWindow/ipc.ts
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.spec.{ts,tsx} : Renderer specs use `*.spec.ts` / `*.spec.tsx`.

Applied to files:

  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to src/**/*.{spec.ts,spec.tsx} : Renderer test files should be placed in nested module paths such as `src/<module>/<subdir>/*.spec.ts(x)` so Jest discovers them.

Applied to files:

  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/app/main/persistence.main.spec.ts
📚 Learning: 2026-08-12T14:11:17.209Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat.Electron PR: 3444
File: src/logViewerWindow/logViewerWindow.tsx:229-238
Timestamp: 2026-08-12T14:11:17.209Z
Learning: In `src/logViewerWindow/logViewerWindow.tsx`, `FacetSelection` uses `null` to mean all options are selected and `[]` to mean no options are selected. When pruning persisted server filters removes every host, the code must store `null`, not `[]`, to prevent `matchesServer` from excluding all log entries.

Applied to files:

  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/logViewerWindow.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.{ts,tsx} : Use Fuselage components for all UI work; create custom components only when Fuselage lacks the required functionality.

Applied to files:

  • src/ui/windowChrome/TextButton.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.{ts,tsx} : Use Fuselage components from `rocket.chat/fuselage` for UI work unless the design requires something Fuselage does not provide.

Applied to files:

  • src/ui/windowChrome/TextButton.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.{ts,tsx} : Import Fuselage components from `rocket.chat/fuselage`.

Applied to files:

  • src/ui/windowChrome/TextButton.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogStatusBar.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to qa/**/*.md : Describe screen region, relative position, icon shape, nearby UI, visible text, and confirmation state in QA steps.

Applied to files:

  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
📚 Learning: 2026-07-09T13:51:14.404Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: qa/AGENTS.md:0-0
Timestamp: 2026-07-09T13:51:14.404Z
Learning: Applies to qa/**/flows/*.md : Use the implementation as the source of truth for visible steps; for Rocket.Chat Desktop UI, inspect the React component tree, Fuselage icon names, translation keys, menu action definitions, modal button labels, and platform guards; for browser helpers, inspect the committed HTML; for OS behavior, inspect the branch code/tests that determine the expected prompt, settings button, registry/default-app state, or desktop integration.

Applied to files:

  • src/documentViewerWindow/DocumentViewerWindow.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.{tsx,jsx} : Use React functional components with hooks.

Applied to files:

  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
📚 Learning: 2026-06-26T18:14:16.585Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3358
File: src/ui/components/SettingsView/features/ToggleField.tsx:1-8
Timestamp: 2026-06-26T18:14:16.585Z
Learning: In the App settings UI for `src/ui/components/SettingsView/features/ToggleField.tsx` in Rocket.Chat Electron, the Fuselage three-tier field structure `FieldLabel` / `FieldDescription` / `FieldHint` is intentionally required by the UXDQA spec: `FieldDescription` carries the regular secondary body text, while `FieldHint` is reserved for the smaller dimmer subline such as restart caveats, so they should not be collapsed into a single hint tier.

Applied to files:

  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.{ts,tsx} : Use React functional components with hooks.

Applied to files:

  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/settingsWindow/SettingsSidebar.tsx
📚 Learning: 2026-07-09T13:51:14.404Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: qa/AGENTS.md:0-0
Timestamp: 2026-07-09T13:51:14.404Z
Learning: Applies to qa/**/scripts/*.mjs : Keep exporters deterministic and dependency-light, using Node built-ins plus existing project dependencies only.

Applied to files:

  • src/app/main/persistence.main.spec.ts
📚 Learning: 2026-08-12T14:11:43.869Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat.Electron PR: 3444
File: src/settingsWindow/sections/GeneralSection.tsx:16-16
Timestamp: 2026-08-12T14:11:43.869Z
Learning: In Rocket.Chat.Electron renderer windows that run with `nodeIntegration: true`, direct `process.platform` and `process.mas` access is intentional. Do not require optional-chaining fallbacks for these properties. The optional-chaining guidance applies to Linux-only process APIs such as `process.getuid()`, `process.getgid()`, `process.geteuid()`, and `process.getegid()`. A fallback for `process.platform` or `process.mas` can silently select an incorrect UI layout and hide a renderer configuration error.

Applied to files:

  • src/downloadsWindow/ipc.ts
📚 Learning: 2026-06-26T18:14:11.817Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3358
File: src/ui/components/SettingsView/features/E2ePdfPreviewSizeLimit.tsx:47-55
Timestamp: 2026-06-26T18:14:11.817Z
Learning: In the Rocket.Chat Electron App SettingsView features under `src/ui/components/SettingsView/features/`, treat full-width selects/inputs (including full-width numeric inputs) as intentional for the stacked label/description layout. Per the UXDQA Figma spec (and macOS 1:1 verification), reviews should not flag these as layout regressions as long as they match the expected form-column stretching behavior.

Applied to files:

  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
📚 Learning: 2026-06-26T18:14:13.838Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3358
File: src/ui/components/SettingsView/features/ToggleField.tsx:1-8
Timestamp: 2026-06-26T18:14:13.838Z
Learning: In Rocket.Chat Electron App settings field UIs that use the Fuselage three-tier pattern, keep the `FieldLabel` / `FieldDescription` / `FieldHint` structure separate. Use `FieldDescription` for the regular secondary body text, and reserve `FieldHint` for the smaller, dimmer subline content (e.g., restart caveats). Do not collapse `FieldDescription` and `FieldHint` into a single hint tier, as this violates the intended UXDQA spec.

Applied to files:

  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.{ts,tsx} : Check `Theme.d.ts` for valid color tokens before using Fuselage colors.

Applied to files:

  • src/logViewerWindow/logViewerWindow.tsx
🔇 Additional comments (22)
src/app/main/persistence.main.spec.ts (1)

43-109: LGTM!

src/documentViewerWindow/DocumentViewerWindow.tsx (1)

1-60: LGTM!

Also applies to: 84-212

src/documentViewerWindow/main/ipc.main.spec.ts (1)

1-324: LGTM!

src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx (1)

1-110: LGTM!

src/downloadsWindow/ipc.ts (1)

4-5: LGTM!

Also applies to: 17-17, 33-33, 158-224

src/downloadsWindow/main/ipc.main.spec.ts (1)

1-179: LGTM!

src/settingsWindow/main/ipc.main.spec.ts (1)

1-189: LGTM!

src/settingsWindow/sections/CertificateRow.spec.tsx (1)

1-78: LGTM!

src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx (1)

1-125: LGTM!

src/ui/main/secondaryWindowFocus.ts (1)

1-15: LGTM!

src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx (1)

1-57: LGTM!

src/logViewerWindow/LogStatusBar.tsx (1)

1-1: LGTM!

Also applies to: 20-72

src/logViewerWindow/LogTimeline.tsx (1)

2-12: LGTM!

Also applies to: 47-102, 109-150, 152-205, 221-323

src/logViewerWindow/LogViewerSidebar.tsx (1)

1-7: LGTM!

Also applies to: 55-242

src/logViewerWindow/__tests__/LogTimeline.spec.tsx (1)

1-110: LGTM!

src/logViewerWindow/__tests__/displayControls.spec.tsx (1)

1-126: LGTM!

src/logViewerWindow/styles.tsx (1)

9-9: LGTM!

Also applies to: 22-74

src/settingsWindow/SettingsSidebar.tsx (1)

3-18: LGTM!

Also applies to: 49-96, 127-177

src/settingsWindow/__tests__/SettingsSidebar.spec.tsx (1)

1-228: LGTM!

src/settingsWindow/__tests__/SettingsWindow.spec.tsx (1)

1-106: LGTM!

src/ui/windowChrome/TextButton.tsx (1)

18-46: LGTM!

src/ui/windowChrome/useCopiedFeedback.ts (1)

1-99: LGTM!

Comment thread src/app/main/persistence.ts
Comment thread src/documentViewerWindow/DocumentViewerWindow.tsx
Comment thread src/logViewerWindow/logViewerWindow.tsx
elapsed === 1000ms used to schedule a zero-delay trailing write
instead of writing now. The comparison is inclusive, with a
boundary test.
A thrown save IPC left an unhandled rejection and no Callout.
handleDownload now catches and shows the existing download error.
A failed refresh no longer leaves the old timeline and list under
the error Callout. Opening another file clears the time range.
Overlapping read-logs responses are ignored via a request id.
Sidebar headings and day headers use one SectionLabel. Cmd/Ctrl+F
focuses the local search field in logs, downloads, and settings.
Day-header blur runs only over a vibrant window, and scrollbar
thumbs follow currentColor instead of theme rgba.
Row IconButtons keep Fuselage's glyph chain. The document-viewer
title icon matches the other toolbars. File-type labels no longer
force weight 700. The certificates list uses stroke and radius
tokens instead of a 4px literal frame.
Register the PDF click interceptor once and remove it on cleanup.
Restore vertical padding on the title-bar downloads panel. Mime
filters share one key set so the old in-window view matches the
downloads window.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/logViewerWindow/logViewerWindow.tsx (1)

682-686: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Serialize and invalidate incremental log reads.

checkForUpdates runs from setInterval but is asynchronous. Two calls can capture the same previousSize and read the same bytes. Both callbacks can then prepend the same entries. The new parseGenerationRef values give duplicate entries different IDs, so the list cannot remove them.

A tail response can also apply after loadLogs() or after a file switch because loadRequestIdRef only guards full loads. Add a single-flight guard and a tail request token. Check the token and selected-file identity before applying the response. Invalidate the token when a full load or file switch starts. Add a delayed-overlap regression test.

🐛 Proposed fix
+const tailRequestIdRef = useRef(0);
+const tailInFlightRef = useRef(false);

 const checkForUpdates = useCallback(async () => {
   if (!isStreaming || !currentLogFile.isDefaultLog) return;
+  if (tailInFlightRef.current) return;
+  tailInFlightRef.current = true;
+  const tailRequestId = ++tailRequestIdRef.current;
   try {
     // existing stat and tail reads
+    if (tailRequestId !== tailRequestIdRef.current) return;
     // apply newEntries, newSize, and lastModifiedTime
+  } finally {
+    tailInFlightRef.current = false;
   }

Also increment tailRequestIdRef when loadLogs() and either file-switch handler starts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/logViewerWindow/logViewerWindow.tsx` around lines 682 - 686, Serialize
asynchronous checkForUpdates calls with a single-flight guard so overlapping
intervals cannot read or prepend the same bytes. Add a tail request token,
invalidate it when loadLogs() or either file-switch handler starts, and before
applying tailResponse verify the token and selected-file identity still match.
Keep parseGenerationRef updates only for responses that pass these checks, and
add a delayed-overlap regression test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/logViewerWindow/logViewerWindow.tsx`:
- Around line 682-686: Serialize asynchronous checkForUpdates calls with a
single-flight guard so overlapping intervals cannot read or prepend the same
bytes. Add a tail request token, invalidate it when loadLogs() or either
file-switch handler starts, and before applying tailResponse verify the token
and selected-file identity still match. Keep parseGenerationRef updates only for
responses that pass these checks, and add a delayed-overlap regression test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c2eb50a-2d18-4271-b40d-b811d0c9f835

📥 Commits

Reviewing files that changed from the base of the PR and between 1103968 and 66c9314.

📒 Files selected for processing (22)
  • src/app/main/persistence.main.spec.ts
  • src/app/main/persistence.ts
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/downloadsWindow/DownloadRow.tsx
  • src/downloadsWindow/DownloadsSidebar.tsx
  • src/downloadsWindow/DownloadsWindow.tsx
  • src/downloadsWindow/FileTypeIcon.tsx
  • src/i18n/en.i18n.json
  • src/logViewerWindow/LogEntry.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/logViewerWindow.tsx
  • src/settingsWindow/SettingsSidebar.tsx
  • src/settingsWindow/sections/CertificateRow.tsx
  • src/settingsWindow/sections/CertificatesSection.tsx
  • src/ui/components/DownloadsManagerView/index.tsx
  • src/ui/components/ServersView/PdfContent.tsx
  • src/ui/components/TopBar/DownloadsIndicator.tsx
  • src/ui/windowChrome/DayHeader.tsx
  • src/ui/windowChrome/FilterSection.tsx
  • src/ui/windowChrome/SectionLabel.tsx
  • src/ui/windowChrome/styles.tsx
  • src/ui/windowChrome/useFindShortcut.ts
💤 Files with no reviewable changes (4)
  • src/settingsWindow/sections/CertificateRow.tsx
  • src/downloadsWindow/FileTypeIcon.tsx
  • src/downloadsWindow/DownloadRow.tsx
  • src/i18n/en.i18n.json
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/ui/windowChrome/FilterSection.tsx
  • src/ui/windowChrome/styles.tsx
  • src/ui/components/TopBar/DownloadsIndicator.tsx
  • src/downloadsWindow/DownloadsWindow.tsx
  • src/app/main/persistence.main.spec.ts
  • src/settingsWindow/SettingsSidebar.tsx
  • src/app/main/persistence.ts
  • src/logViewerWindow/LogEntry.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/downloadsWindow/DownloadsSidebar.tsx
  • src/settingsWindow/sections/CertificatesSection.tsx
  • src/ui/components/ServersView/PdfContent.tsx
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: check (windows-latest)
  • GitHub Check: check (ubuntu-latest)
  • GitHub Check: build (ubuntu-latest, linux)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for new code unless explicitly told otherwise.
Use Fuselage components from @rocket.chat/fuselage for UI work unless the design requires something Fuselage does not provide.
Check Theme.d.ts for valid color tokens before using Fuselage colors.
Verify library props, APIs, and tokens against official docs or local .d.ts files instead of assuming.
Use React functional components with hooks.
Redux actions follow FSA shape.
Use camelCase for file names and PascalCase for components.
Prefer clear names over unnecessary comments.
Prefer editing existing files over creating new abstractions unless the new abstraction removes real complexity or matches an existing pattern.

**/*.{ts,tsx}: Use TypeScript for all new code unless explicitly told otherwise.
Use Fuselage components for all UI work; create custom components only when Fuselage lacks the required functionality.
Import Fuselage components from @rocket.chat/fuselage.
Use only valid color tokens documented by Theme.d.ts.
Use optional chaining with fallbacks for platform-specific APIs, especially Linux-only process APIs such as process.getuid(), getgid(), geteuid(), and getegid().
Use TypeScript strict mode.
Redux actions must follow the Flux Standard Action pattern.
Use camelCase for file names and PascalCase for component names.
Avoid unnecessary comments; prefer self-documenting code through clear naming.
Do not commit or push without explicit user permission.
Verify library APIs, props, tokens, and types against official documentation and .d.ts files instead of assuming they are valid.

Files:

  • src/ui/components/DownloadsManagerView/index.tsx
  • src/ui/windowChrome/useFindShortcut.ts
  • src/ui/windowChrome/DayHeader.tsx
  • src/ui/windowChrome/SectionLabel.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/logViewerWindow.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use React functional components with hooks.

Files:

  • src/ui/components/DownloadsManagerView/index.tsx
  • src/ui/windowChrome/DayHeader.tsx
  • src/ui/windowChrome/SectionLabel.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/logViewerWindow.tsx
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: qa/AGENTS.md:0-0
Timestamp: 2026-07-09T13:51:14.404Z
Learning: Applies to qa/** : Classify changed Desktop surfaces by user-visible risk, including Electron main process, protocol handlers, OS default handlers, settings UI, menus, modals, packaging/installers, startup, shortcuts, workspace routing, i18n, and layout.
📚 Learning: 2026-08-12T14:11:40.244Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat.Electron PR: 3444
File: src/settingsWindow/sections/GeneralSection.tsx:16-16
Timestamp: 2026-08-12T14:11:40.244Z
Learning: In Rocket.Chat.Electron renderer TypeScript/TSX files running with nodeIntegration enabled, direct access to process.platform and process.mas is intentional; do not require optional-chaining fallbacks for these properties. Optional-chaining safeguards should apply to Linux-only APIs such as process.getuid(), process.getgid(), process.geteuid(), and process.getegid(). Adding fallbacks for process.platform or process.mas can silently select an incorrect UI layout and conceal a renderer configuration error.

Applied to files:

  • src/ui/components/DownloadsManagerView/index.tsx
  • src/ui/windowChrome/DayHeader.tsx
  • src/ui/windowChrome/SectionLabel.tsx
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/logViewerWindow/logViewerWindow.tsx
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.{ts,tsx} : Use React functional components with hooks.

Applied to files:

  • src/ui/windowChrome/useFindShortcut.ts
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.{tsx,jsx} : Use React functional components with hooks.

Applied to files:

  • src/ui/windowChrome/useFindShortcut.ts
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.{ts,tsx} : Use Fuselage components for all UI work; create custom components only when Fuselage lacks the required functionality.

Applied to files:

  • src/ui/windowChrome/SectionLabel.tsx
📚 Learning: 2026-06-26T18:14:16.585Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3358
File: src/ui/components/SettingsView/features/ToggleField.tsx:1-8
Timestamp: 2026-06-26T18:14:16.585Z
Learning: In the App settings UI for `src/ui/components/SettingsView/features/ToggleField.tsx` in Rocket.Chat Electron, the Fuselage three-tier field structure `FieldLabel` / `FieldDescription` / `FieldHint` is intentionally required by the UXDQA spec: `FieldDescription` carries the regular secondary body text, while `FieldHint` is reserved for the smaller dimmer subline such as restart caveats, so they should not be collapsed into a single hint tier.

Applied to files:

  • src/ui/windowChrome/SectionLabel.tsx
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.{ts,tsx} : Use Fuselage components from `rocket.chat/fuselage` for UI work unless the design requires something Fuselage does not provide.

Applied to files:

  • src/ui/windowChrome/SectionLabel.tsx
📚 Learning: 2026-07-10T13:16:09.853Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-07-10T13:16:09.853Z
Learning: Applies to **/*.{ts,tsx} : Import Fuselage components from `rocket.chat/fuselage`.

Applied to files:

  • src/ui/windowChrome/SectionLabel.tsx
📚 Learning: 2026-08-03T18:47:17.146Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3431
File: src/updates/main.ts:0-0
Timestamp: 2026-08-03T18:47:17.146Z
Learning: In `src/updates/main.ts`, `electron-updater` installation failures from `autoUpdater.quitAndInstall()` normally emit the `error` event through `BaseUpdater.dispatchError`; the `autoUpdater.addListener('error', ...)` handler is the primary update-installation error-reporting path. The local `quitAndInstall` catch is only a backstop for unexpected synchronous throws.

Applied to files:

  • src/documentViewerWindow/DocumentViewerWindow.tsx
📚 Learning: 2026-03-11T06:38:40.426Z
Learnt from: Ram-sah19
Repo: RocketChat/Rocket.Chat.Electron PR: 3254
File: .github/workflows/build-release.yml:80-94
Timestamp: 2026-03-11T06:38:40.426Z
Learning: In the RocketChat/Rocket.Chat.Electron repository, the issues flagged in `.github/workflows/build-release.yml` (e.g., `node12` runtime in the release action and missing `snapcraft_token` input), i18n files, and `electron-builder.json` are pre-existing in the `develop` branch and are pulled in during merge conflict resolution. Do not flag these as new issues introduced by PRs that only modify `src/injected.ts` and `src/ui/main/rootWindow.ts`.

Applied to files:

  • src/documentViewerWindow/DocumentViewerWindow.tsx
📚 Learning: 2026-08-12T14:11:17.209Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat.Electron PR: 3444
File: src/logViewerWindow/logViewerWindow.tsx:229-238
Timestamp: 2026-08-12T14:11:17.209Z
Learning: In `src/logViewerWindow/logViewerWindow.tsx`, `FacetSelection` uses `null` to mean all options are selected and `[]` to mean no options are selected. When pruning persisted server filters removes every host, the code must store `null`, not `[]`, to prevent `matchesServer` from excluding all log entries.

Applied to files:

  • src/logViewerWindow/logViewerWindow.tsx
📚 Learning: 2026-07-09T13:50:56.290Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-09T13:50:56.290Z
Learning: Applies to **/*.{ts,tsx} : Check `Theme.d.ts` for valid color tokens before using Fuselage colors.

Applied to files:

  • src/logViewerWindow/logViewerWindow.tsx
🪛 React Doctor (0.9.3)
src/logViewerWindow/logViewerWindow.tsx

[error] 344-344: This resets a loading/busy flag only on the success path: if the awaited call rejects the reset never runs and the flag stays stuck truthy (a spinner that never stops, a button disabled forever). Move the reset into a finally block, or mirror it on every catch, so it clears on rejection too.

A trailing setLoading(false) after an await never runs if the awaited call rejects, so the flag stays stuck truthy; reset it in a finally block (or mirror the reset on every catch) so it clears on both paths.

(no-loading-flag-reset-outside-finally)

🔇 Additional comments (10)
src/documentViewerWindow/DocumentViewerWindow.tsx (1)

24-60: LGTM!

Also applies to: 62-94, 96-171, 173-190, 192-224

src/ui/components/DownloadsManagerView/index.tsx (1)

79-83: LGTM!

src/ui/windowChrome/DayHeader.tsx (1)

4-11: LGTM!

Also applies to: 25-51

src/ui/windowChrome/SectionLabel.tsx (1)

1-18: LGTM!

src/ui/windowChrome/useFindShortcut.ts (1)

1-17: LGTM!

src/logViewerWindow/logViewerWindow.tsx (5)

138-138: LGTM!

Also applies to: 259-260, 273-273, 328-341


773-776: LGTM!


817-818: LGTM!

Also applies to: 836-837


873-874: LGTM!

Also applies to: 884-884


1055-1062: LGTM!

Also applies to: 1113-1128

The 2px bar had copied ProgressBar's status-font-on-info token, a
text color used as a fill. It now uses font-info, the accent token
for progress fills.
@jeanfbrito
jeanfbrito changed the base branch from master to dev August 13, 2026 14:21
@jeanfbrito
jeanfbrito merged commit d1b25f4 into dev Aug 13, 2026
16 of 18 checks passed
@jeanfbrito
jeanfbrito deleted the feat/log-viewer-revamp branch August 13, 2026 14:33
cursor Bot pushed a commit that referenced this pull request Aug 28, 2026
Rebased onto current origin/dev after #3444. Keeps the #3444 shell
(sidebar/timeline/toolbar) and ports the additive hardening from this PR:

- Tail reads stop at the last newline (trimBufferToLastNewline) so
  mid-poll writes cannot split lines/UTF-8 characters
- fs.promises everywhere in log-viewer handlers (no existsSync/statSync
  blocking the main event loop)
- Precompute searchText/rawLower at parse time for filter matching
- Auto-scroll timestamp guard so programmatic scrolls do not cancel
  themselves; resume pill when paused with new entries pending
- Reveal log file in folder (authorized IPC → shell.showItemInFolder)
- Save as plain .log alongside zip; i18n for native file dialogs
- isAtLeastLevel helper retained for callers; UI keeps #3444 facet
  multi-select level filters

Co-authored-by: Jean Brito <jeanfbrito@gmail.com>
jeanfbrito added a commit that referenced this pull request Aug 28, 2026
Rebased onto current origin/dev after #3444. Keeps the #3444 shell
(sidebar/timeline/toolbar) and ports the additive hardening from this PR:

- Tail reads stop at the last newline (trimBufferToLastNewline) so
  mid-poll writes cannot split lines/UTF-8 characters
- fs.promises everywhere in log-viewer handlers (no existsSync/statSync
  blocking the main event loop)
- Precompute searchText/rawLower at parse time for filter matching
- Auto-scroll timestamp guard so programmatic scrolls do not cancel
  themselves; resume pill when paused with new entries pending
- Reveal log file in folder (authorized IPC → shell.showItemInFolder)
- Save as plain .log alongside zip; i18n for native file dialogs
- isAtLeastLevel helper retained for callers; UI keeps #3444 facet
  multi-select level filters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants