feat: log viewer, downloads, settings and document viewer as separate windows on a shared native shell - #3444
Conversation
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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📜 Recent review details⏰ Context from checks skipped due to timeout. (3)
WalkthroughThis 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. ChangesSecondary windows and shared foundation
Document viewer
Downloads
Log viewer
Settings
About and existing UI integration
Estimated code review effort: 5 (Critical) | ~120 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
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. Comment |
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>
Linux installer download |
macOS installer download |
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>
…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>
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.
There was a problem hiding this comment.
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 winReset
timeRangewhen the log source changes.Line 185 states that the range belongs to the current file.
handleOpenLogFileandhandleOpenDefaultLogchange the source but retaintimeRange. If the ranges do not overlap, the new file shows no entries.Clear
timeRangebefore 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 winDiscard responses from older load requests.
loadLogscan overlap when the user changes files or requests a refresh. An olderread-logsrequest can resolve last and overwritelogEntries,fileInfo, andcurrentLogFilefor the newer source.Use a monotonically increasing request identifier. Guard every response state update, including
setIsLoading(false), against the latest request identifier.parseGenerationRefonly 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
📒 Files selected for processing (43)
src/app/main/persistence.main.spec.tssrc/app/main/persistence.tssrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/documentViewerWindow/ipc.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/downloadsWindow/DownloadRow.tsxsrc/downloadsWindow/DownloadsWindow.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/downloadsWindow/ipc.tssrc/downloadsWindow/main/ipc.main.spec.tssrc/i18n/en.i18n.jsonsrc/ipc/channels.tssrc/logViewerWindow/LogEntry.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogViewerToolbar.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/logViewerWindow/appearance.tssrc/logViewerWindow/ipc.tssrc/logViewerWindow/logViewerWindow.tsxsrc/logViewerWindow/styles.tsxsrc/main.tssrc/settingsWindow/SettingsSidebar.tsxsrc/settingsWindow/SettingsWindow.tsxsrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/settingsWindow/__tests__/SettingsWindow.spec.tsxsrc/settingsWindow/ipc.tssrc/settingsWindow/main/ipc.main.spec.tssrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/settingsWindow/sections/CertificateRow.tsxsrc/ui/components/SettingsView/features/CheckForUpdates.spec.tsxsrc/ui/components/SettingsView/features/CheckForUpdates.tsxsrc/ui/main/secondaryWindowFocus.tssrc/ui/windowChrome/NavRow.tsxsrc/ui/windowChrome/TextButton.tsxsrc/ui/windowChrome/__tests__/copiedFeedback.spec.tsxsrc/ui/windowChrome/__tests__/filters.spec.tssrc/ui/windowChrome/appearance.tssrc/ui/windowChrome/filters.tssrc/ui/windowChrome/styles.tsxsrc/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/fuselagefor UI work unless the design requires something Fuselage does not provide.
CheckTheme.d.tsfor valid color tokens before using Fuselage colors.
Verify library props, APIs, and tokens against official docs or local.d.tsfiles 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 byTheme.d.ts.
Use optional chaining with fallbacks for platform-specific APIs, especially Linux-only process APIs such asprocess.getuid(),getgid(),geteuid(), andgetegid().
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.tsfiles instead of assuming they are valid.
Files:
src/ui/main/secondaryWindowFocus.tssrc/settingsWindow/__tests__/SettingsWindow.spec.tsxsrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/settingsWindow/main/ipc.main.spec.tssrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/ui/windowChrome/TextButton.tsxsrc/ui/windowChrome/__tests__/copiedFeedback.spec.tsxsrc/ui/windowChrome/useCopiedFeedback.tssrc/downloadsWindow/main/ipc.main.spec.tssrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/documentViewerWindow/main/ipc.main.spec.tssrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/app/main/persistence.main.spec.tssrc/downloadsWindow/ipc.tssrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/settingsWindow/SettingsSidebar.tsxsrc/ui/components/SettingsView/features/CheckForUpdates.spec.tsxsrc/app/main/persistence.tssrc/logViewerWindow/styles.tsxsrc/logViewerWindow/logViewerWindow.tsx
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Renderer specs use
*.spec.ts/*.spec.tsx.
Files:
src/settingsWindow/__tests__/SettingsWindow.spec.tsxsrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/settingsWindow/main/ipc.main.spec.tssrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/ui/windowChrome/__tests__/copiedFeedback.spec.tsxsrc/downloadsWindow/main/ipc.main.spec.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/app/main/persistence.main.spec.tssrc/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); flatsrc/<module>/*.spec.tsfiles are not discovered by the currenttestMatch.
Files:
src/settingsWindow/__tests__/SettingsWindow.spec.tsxsrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/settingsWindow/main/ipc.main.spec.tssrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/downloadsWindow/main/ipc.main.spec.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/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.tsxsrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/settingsWindow/main/ipc.main.spec.tssrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/ui/windowChrome/__tests__/copiedFeedback.spec.tsxsrc/downloadsWindow/main/ipc.main.spec.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/app/main/persistence.main.spec.tssrc/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.tsxsrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/ui/windowChrome/TextButton.tsxsrc/ui/windowChrome/__tests__/copiedFeedback.spec.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/settingsWindow/SettingsSidebar.tsxsrc/ui/components/SettingsView/features/CheckForUpdates.spec.tsxsrc/logViewerWindow/styles.tsxsrc/logViewerWindow/logViewerWindow.tsx
**/*.main.spec.ts
📄 CodeRabbit inference engine (AGENTS.md)
Main-process specs use
*.main.spec.ts.Use
*.main.spec.tsfor main process tests.
Files:
src/settingsWindow/main/ipc.main.spec.tssrc/downloadsWindow/main/ipc.main.spec.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/app/main/persistence.main.spec.ts
**/*.spec.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use
*.spec.tsfor renderer process tests.
Files:
src/settingsWindow/main/ipc.main.spec.tssrc/downloadsWindow/main/ipc.main.spec.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/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.tsxsrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/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.tsxsrc/settingsWindow/sections/CertificateRow.spec.tsxsrc/logViewerWindow/__tests__/LogTimeline.spec.tsxsrc/downloadsWindow/__tests__/DownloadsWindow.spec.tsxsrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/ui/windowChrome/TextButton.tsxsrc/ui/windowChrome/__tests__/copiedFeedback.spec.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/settingsWindow/SettingsSidebar.tsxsrc/ui/components/SettingsView/features/CheckForUpdates.spec.tsxsrc/logViewerWindow/styles.tsxsrc/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.tsxsrc/settingsWindow/main/ipc.main.spec.tssrc/logViewerWindow/__tests__/displayControls.spec.tsxsrc/documentViewerWindow/main/ipc.main.spec.tssrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/app/main/persistence.main.spec.tssrc/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.tssrc/documentViewerWindow/main/ipc.main.spec.tssrc/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.tssrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/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.tssrc/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.tsxsrc/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.tsxsrc/settingsWindow/__tests__/SettingsSidebar.spec.tsxsrc/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.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/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.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/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.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/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.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/LogStatusBar.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/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.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/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.tsxsrc/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.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/LogTimeline.tsxsrc/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!
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.
There was a problem hiding this comment.
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 liftSerialize and invalidate incremental log reads.
checkForUpdatesruns fromsetIntervalbut is asynchronous. Two calls can capture the samepreviousSizeand read the same bytes. Both callbacks can then prepend the same entries. The newparseGenerationRefvalues give duplicate entries different IDs, so the list cannot remove them.A tail response can also apply after
loadLogs()or after a file switch becauseloadRequestIdRefonly 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
tailRequestIdRefwhenloadLogs()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
📒 Files selected for processing (22)
src/app/main/persistence.main.spec.tssrc/app/main/persistence.tssrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/downloadsWindow/DownloadRow.tsxsrc/downloadsWindow/DownloadsSidebar.tsxsrc/downloadsWindow/DownloadsWindow.tsxsrc/downloadsWindow/FileTypeIcon.tsxsrc/i18n/en.i18n.jsonsrc/logViewerWindow/LogEntry.tsxsrc/logViewerWindow/LogViewerSidebar.tsxsrc/logViewerWindow/logViewerWindow.tsxsrc/settingsWindow/SettingsSidebar.tsxsrc/settingsWindow/sections/CertificateRow.tsxsrc/settingsWindow/sections/CertificatesSection.tsxsrc/ui/components/DownloadsManagerView/index.tsxsrc/ui/components/ServersView/PdfContent.tsxsrc/ui/components/TopBar/DownloadsIndicator.tsxsrc/ui/windowChrome/DayHeader.tsxsrc/ui/windowChrome/FilterSection.tsxsrc/ui/windowChrome/SectionLabel.tsxsrc/ui/windowChrome/styles.tsxsrc/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/fuselagefor UI work unless the design requires something Fuselage does not provide.
CheckTheme.d.tsfor valid color tokens before using Fuselage colors.
Verify library props, APIs, and tokens against official docs or local.d.tsfiles 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 byTheme.d.ts.
Use optional chaining with fallbacks for platform-specific APIs, especially Linux-only process APIs such asprocess.getuid(),getgid(),geteuid(), andgetegid().
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.tsfiles instead of assuming they are valid.
Files:
src/ui/components/DownloadsManagerView/index.tsxsrc/ui/windowChrome/useFindShortcut.tssrc/ui/windowChrome/DayHeader.tsxsrc/ui/windowChrome/SectionLabel.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/logViewerWindow/logViewerWindow.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use React functional components with hooks.
Files:
src/ui/components/DownloadsManagerView/index.tsxsrc/ui/windowChrome/DayHeader.tsxsrc/ui/windowChrome/SectionLabel.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/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.tsxsrc/ui/windowChrome/DayHeader.tsxsrc/ui/windowChrome/SectionLabel.tsxsrc/documentViewerWindow/DocumentViewerWindow.tsxsrc/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.
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>
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
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.
transparentcannot 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 becausemove/resizefire 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
Downloads
Settings
vibrhit "Video calls"), so longer text falls back to substring.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
PdfContentannounced its webview to the main process ondid-attach, butgetWebContentsId()throws until the guest document exists, so the call threw every time and the announcement never arrived. It now announces ondom-ready.FieldGroup's rhythm — the PDF size limit sat 16px below its neighbour while everything else sat at 24. Worse,TelephonyGlobalShortcutnever accepted theclassNamethatFieldGrouppasses down, so its rows had no gap at all. Seven components that hand-rolledFieldmarkup now use the sharedSettingField/ToggleFieldwrappers. Measured on the built window: every gap within a group is 24px, every gap across a divider 49px.DownloadsIndicatorreadsDate.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.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 --noEmitandyarn testall 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.platformto check the Windows chrome — rather than eyeballing screenshots.Not covered by automated tests, and worth a look during review:
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 workspaceNotes
SettingsView,DownloadsManagerView,CertificatesManagerand the in-paneDocumentViewerare 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 tosettings.*would orphan every existing translation in the other locales.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes