Release 4.17.0 - #3483
Merged
Merged
Conversation
… windows on a shared native shell (#3444) * feat(log-viewer): sidebar filters, native window chrome, paginated list 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> * feat(log-viewer): distribution timeline with drag range selection 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> * feat(log-viewer): inset log card, live transparency, restore on launch 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> * feat(downloads): move downloads into its own window on shared chrome 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> * feat(downloads): grey document icon for file types 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> * feat(settings): settings in its own window, with remembered positions 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> * feat(windows): in-app title bar on Windows, and room for the theme options 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> * feat(settings): fold About into settings, and one spacing rhythm 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> * fix(test): drive the clock in the downloads indicator seen test 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> * feat(document-viewer): open PDFs and markdown in their own window 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> * feat(document-viewer): download the file, and read markdown as source 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> * refactor(chrome): one title bar height across every window 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> * fix(chrome): stop the filter checkbox toggling twice 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> * feat(chrome): let a filter be cleared completely 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> * fix(windows): stop secondary windows opening full screen 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> * feat(downloads): show the same download row in the top bar panel 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> * fix(test): keep the downloads panel size test inside Jest's timeout 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> * fix: address review findings on the secondary windows Two of these are user-visible. The log viewer's server filter could hide every entry. Making an empty facet selection mean "nothing selected" gave the pruning effect a way to empty the list: when no persisted host survived, it set `[]`. It falls back to untouched now, which is what the effect was there to guarantee. A profile saved before downloads and settings moved into windows still names one of them as the root window's view. Restoring it stranded the reader there, since the sidebar buttons that used to leave those views now open windows. Such a value is ignored on load. The focus-request branch that returned 'downloads' goes too — the downloads notification opens the window through SIDE_BAR_DOWNLOADS_BUTTON_CLICKED and nothing dispatches it. The rest is hardening. Each window guarded on its module variable and then awaited the main window before assigning it, so two opens arriving in that gap each built a window and the second orphaned the first — visible, untracked, unclosable from its own channel. Every caller now passes through one gate holding a single in-flight promise, `restore*` included. The document viewer's listener catches instead of leaving an unhandled rejection. Reading a blob to save it used a per-byte loop and btoa in the reader's own workspace, freezing that window for the length of the file and holding it three times over. FileReader does it natively; checked byte for byte over the full 0-255 range. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(windows): restore minimized secondary windows on reopen 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. * fix(document-viewer): surface save errors and stop dropping opens 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. * fix(chrome): keep out-of-universe values visible when facets narrow 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. * fix(settings): settle update checks properly and keep search ephemeral 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. * fix(app): never drop the last persisted state write 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. * fix(log-viewer): surface load failures and save feedback 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. * fix(log-viewer): give the timeline real slider keyboard semantics 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. * fix(chrome): tokenize secondary-window card shadow and radius 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. * fix(settings): give sidebar tabs real keyboard and selected weight 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. * fix(chrome): tokenize hover fills and announce copy and save 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". * fix(log-viewer): use system tokens and distinguish display toggles 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. * fix(windows): confirm destructive clears and tokenize progress 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. * fix(app): persist immediately when the throttle interval elapses elapsed === 1000ms used to schedule a zero-delay trailing write instead of writing now. The comparison is inclusive, with a boundary test. * fix(document-viewer): surface rejected save invocations A thrown save IPC left an unhandled rejection and no Callout. handleDownload now catches and shows the existing download error. * fix(log-viewer): drop stale loads and hide logs on 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. * fix(chrome): share section labels and Cmd+F search focus 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. * fix(chrome): drop extra icon colors and literal list chrome 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. * fix(ui): close leftover CodeRabbit polish notes 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. * fix(downloads): use font-info for the row progress fill 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. * fix(settings): answer the secondary-window IPC invoke in window specs --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Jean Brito <jeanfbrito@gmail.com>
#3429) * test: raise full-surface line coverage to 70% with quick-win specs Add and relocate unit/integration specs so Jest discovers them under the current testMatch rules, exercise main IPC and preloads under coverage, and extract small pure helpers for testability. Fixes ErrorView so the failed/reloading UI short-circuits correctly. Documents the new full-src coverage milestone in docs/COVERAGE.md (70.14% lines, 11876-line surface). * test: restore dropped video-call popup/session security assertions The video-call ipc.main.spec.ts rewrite in the previous commit dropped popup URL-scheme denial, shared-vs-isolated session permission-handler wiring, session-partition resolution, restore idempotency, and same-conference reopen coverage with nothing replacing it. Restores the richer mocking harness and those assertions, merged with the newer lifecycle-channel coverage. Also replaces a coverage-padding test in preloadCoverage.spec.ts that only asserted dispatch-was-called with real per-call assertions on the actual dispatched action types and payloads. * test: address CodeRabbit review findings on coverage quick-wins PR Fixes all 5 actionable comments plus the 17 minor and 20 nitpick findings CodeRabbit left on PR #3429: - Renamed main-process specs to the *.main.spec.ts convention (buildAssets, mainEntry, downloads/integration, menuBar, dock) so Jest routes them to the correct project. - Fixed import/first and other lint failures blocking CI (i18n renderer, logViewerWindow, documentViewer, PdfContent). - Replaced weak/no-op assertions across outlookCalendar, setupServers, preloadCoverage, screenSharePicker, FailureImage, TopBar, TooltipProvider, moreSettings, menuBar, dock, PdfContent, and ServerPane specs with real assertions on dispatched actions, DOM state, or IPC payloads so a genuine regression would fail the test. - Fixed a stale fs-mock reference in setupServers.spec.ts that left the app-server fallback branch fully unexercised. - Split an overloaded preload-coverage test into 5 focused tests and consolidated 28 repeated eslint-disable comments into one file-level directive. - Restored security-relevant coverage in videoCallWindow ipc.main.spec.ts (popup scheme denial, session permission wiring) that had been dropped in the original rewrite, plus a real get-provider-sync assertion. * fix: resolve CI lint failures blocking PR checks CI's Lint step (yarn lint = eslint + tsc --noEmit) was failing on all three platforms. Two root causes: - 33 auto-fixable prettier/import-order violations across specs (fixed via `eslint --fix`), plus one naming-convention error in settingsToggles.spec.tsx (a `Component` prop key/destructure conflicted with camelCase parameter rules — renamed the field to `component` and kept a capitalized local alias for JSX usage). - 5 spec files with no top-level import/export statement, causing TypeScript to treat them as global scripts rather than modules. Their same-named top-level consts (`handlers`, `dispatch`, `select`, `watchCallbacks`, `getRootWindow`) collided across files under whole-project `tsc --noEmit`, even though each runs fine in isolation under Jest. Added `export {}` to force module scope. Also fixed a real regression introduced by the eslint --fix pass: it hoisted the `setupUpdates` import in setupUpdates.spec.ts above the `autoUpdater` const its `electron-updater` mock factory closes over, causing "Cannot access 'autoUpdater' before initialization" at runtime. Restored the import to its required position below the const and mocks, with a comment and scoped eslint-disable explaining why the ordering can't be "corrected" by tooling. Verified: `yarn lint` exits 0, full `yarn test` is green (217 suites, 1972 tests, 0 failures). * fix: resolve cross-platform CI test failures on ubuntu and windows CI's ubuntu-latest and windows-latest check jobs failed while macos-latest passed, revealing three real platform-dependent test bugs: - menuBar.main.spec.ts: a new test asserted on the darwin-only 'about' appMenu item without mocking process.platform, so it failed on non-darwin runners. Switched to windowMenu's 'settings' item, which exercises the identical show-if-hidden-then-focus behavior but is registered on every platform. (selectMenuBarTemplate is memoized via createSelector, so a same-test process.platform override can't force recomputation of an already-cached platform-gated item anyway — confirmed by reproducing the failure with a forced-linux variant.) - setupUpdates.spec.ts: isUpdatingAllowed is computed by the real loadConfiguration() selector directly from process.platform/ process.mas/process.windowsStore, never from the mocked store state. On Linux CI (no APPIMAGE env) this is always false, short-circuiting setupUpdates() before it wires up autoUpdater or any listeners. Pinned process.platform to 'win32' (windowsStore: false) in beforeEach/afterEach so the allowed branch runs deterministically on every CI runner. Verified by reproducing the exact 3 CI failures locally with a forced-linux variant, then confirming the fix passes. - logging/__tests__/cleanup.spec.ts (pre-existing, not touched by earlier commits in this branch): asserted unlinkSync was called with a hardcoded forward-slash path, but the real implementation builds the path with path.join(), which uses backslashes on Windows. Switched the assertion to path.join() as well. Verified: yarn lint exits 0, full yarn test is green (217 suites, 1972 tests, 0 failures) locally. * test: rename remaining main-process specs to *.main.spec.ts AGENTS.md requires main-process specs to use the *.main.spec.ts suffix so Jest routes them to the main-process project. Rename the four leftover *.spec.ts files under documentViewer, servers, and updates. Co-authored-by: Jean Brito <jeanfbrito@gmail.com> * fix: adapt coverage specs to current dev APIs after rebase Rebase onto current origin/dev left several quick-win specs targeting removed or rewritten surfaces. Drop the orphaned AboutDialog spec, update log viewer / ServerPane / setupUpdates / settings specs to the current props and action names, and fill in store/electron mocks required by the separate-window log viewer and downloads simulation listener. Co-authored-by: Jean Brito <jeanfbrito@gmail.com> * test: finish leftover CodeRabbit main-spec rename and dock no-op Rename preloadCoverage.spec.ts to the required *.main.spec.ts suffix and assert that dock.setUp() registers no watchers on non-darwin. Co-authored-by: Jean Brito <jeanfbrito@gmail.com> * test: mock app.showAboutPanel in menuBar click-handler coverage The full-template click loop invokes the macOS About item, which calls app.showAboutPanel(). The electron app mock omitted that method, so CI failed with TypeError on that handler. Co-authored-by: Jean Brito <jeanfbrito@gmail.com>
* accept and apply saml auth params from deeplink * add unit tests
…3466) * feat(tray): show workspace presence status in the tray icon and menu Surface the active workspace's presence (online/away/busy/offline) on the tray icon and turn the tray context menu into a presence selector, on Windows and Linux. The app-icon unread badge is unchanged. The web client already exposes presence through Meteor, so `injected.ts` reads it directly and no Rocket.Chat-side change is needed: - read `Meteor.user().status` in a `Tracker.autorun`, plus `Meteor.status()` for connection state, and push both per-workspace - write with `Meteor.call('setUserStatus', status, statusText)`, which sets presence and custom text in one call `setUserStatus` is rate limited to 1 call/sec/user, so tray picks go through a trailing-edge limiter: a burst sends the first request immediately and the last one after the window, so the user's final choice always reaches the server. The tray menu previously rebuilt only when root-window visibility changed. It now rebuilds from a shared refresh driven by presence, custom status, connection state and login state as well, so the items cannot show stale values. Menu state follows the ticket: options are disabled with a "trying to reconnect" line while disconnected, replaced by a sign-in action when logged out, replaced by add-workspace when no workspace exists, and hidden entirely when the workspace does not report presence. The presence checkmark reads `status`, not `statusDefault`: both write paths route through `Presence.setStatus`, which moves `status` and leaves `statusDefault` untouched, so `statusDefault` does not track what the user picked. macOS is deliberately excluded. Its tray assets are template images, which the OS recolors and strips color from, so a colored presence dot needs a separate design decision. Refs CORE-2525 * chore(tray): generate presence tray icon variants for win32 and linux Output of `yarn build-assets` for the presence icons added in the previous commit: 48 `.ico` files for win32 and 96 `.png` files for linux (48 plus their @2x pairs), covering four presence states with and without each unread-badge variant. Existing filenames are untouched, so builds that do not pass a presence value resolve exactly the same assets as before. Refs CORE-2525 * fix(tray): read the custom status message over REST `statusText` is not part of the DDP publication of the user document, so `Meteor.user().statusText` is always undefined and the tray's read-only custom status line never appeared. Fetch it from `users.info` instead. The fetch is cached and runs when a session first appears and after the app requests a presence change, rather than inside the reactive block, which would re-run on every presence change. Because resolving the fetch does not re-run that block, the resolved value re-pushes the latest presence snapshot itself. A failed fetch leaves the previous value in place and never throws: the custom status line is secondary to the presence indicator, which must keep working regardless. Refs CORE-2525 * test(qa): add QA flow pack for tray presence status Eleven flows covering the presence icon states, the menu radio items and their checked state, the read-only custom status line, active-workspace scoping, and the disconnected, logged-out, no-workspace and unsupported cases. Two flows target specific risks rather than happy paths: one proves the unread badge and the presence dot stay distinguishable at native icon size, and one proves a rapid pair of presence clicks ends on the second choice instead of dropping it. A third pins the disconnected state to a real disconnect, so the cold-launch false positive cannot come back unnoticed. Steps assert status changes against the workspace rather than the tray, since a tray that looks right can still have failed to send. Refs CORE-2525 * test(jest): exclude the presence preload spec from coverage runs `yarn test:coverage` fails this suite with `EvalError: Code generation from strings disallowed for this context`. Istanbul instruments the module graph with `new Function(...)` counters, and the Electron BrowserWindow context the renderer specs run in forbids code generation from strings, so the suite dies before any test executes. Ten sibling preload specs are already listed for the same reason, including the `userLoggedIn` spec this one is modelled on. The suite still runs and still gates on pass or fail under `yarn test`. Refs CORE-2525 * fix(tray): read presence from the webapp's presence store The tray never showed a presence dot. `injected.ts` read the user's status from `Meteor.user()`, but Rocket.Chat does not publish presence into the `Meteor.users` collection: presence lives in a standalone store fed by the `stream-user-presence` DDP stream (`apps/meteor/client/lib/presence.ts`). So `status` was always undefined, which failed twice over. The presence value was empty, and `presenceSupported` was computed from whether that value existed — so it resolved to false and the tray hid its presence options as if the workspace did not support them at all. Read the store instead, and subscribe with `Presence.listen`, which is an emitter rather than a Tracker dependency and so lives outside the autorun. `presenceSupported` now reflects whether the module resolved and exposes the API we need, not whether a value happens to be present. Store entries already carry the custom status message, so the REST `users.info` fetch added earlier is gone. That also fixes its staleness: a status changed on another device never refreshed, because the fetch only re-ran on login or on a change this app itself requested. The snapshot computation is extracted so it can be tested without a live workspace, including a regression test for the specific mistake above: a missing status value must not report presence as unsupported. The `window.require` specifier for the presence module is not confirmed against a running workspace, so a short candidate list is probed and the module's shape is checked before use. Every failure path reports presence as unsupported, which the tray already handles by hiding the options. Refs CORE-2525 * fix(tray): seed presence with Presence.get, not the store The presence store is populated lazily: it is empty until something asks for a user id, and subscribing with `listen` alone never backfills it. So reading `store.get(uid)` to seed the first value returned nothing on a fresh session, and the tray kept its default icon until some later presence change happened to arrive. `Presence.get(uid)` resolves the current presence and registers the subscription that keeps it fresh, so it is what seeds the value. Verified against a live 8.8 workspace: it returns the user's status and leaves the store populated. Also pins the module specifier that actually resolves there, `/client/lib/presence.ts`. The extension is required — the extensionless form fails with "Cannot find module" — so an earlier guess based on this file's other specifiers had it backwards. Refs CORE-2525 * feat(tray): show presence colour on the macOS menu bar icon Keep the shipping Template assets for the no-presence path. When presence is known, use a non-template PNG so the badge colour survives, and invert only the black rocket glyph so Liquid Glass still tints it white. * chore(tray): generate presence tray icon variants for darwin Non-template PNGs so the presence colour survives on the menu bar. Template default/notification assets are unchanged. * fix(tray): recolour the shipping Windows/Linux badge for presence Drop the extra PresenceDot slot. Presence uses the original unread Badge overlay, same size and position, with only the fill colour changing. Combined unread+presence keeps the numeral inside that circle. * chore(tray): regenerate win32 and linux presence icons from the badge overlay * style(tray): satisfy prettier in the macOS glyph spec The expect() assertion wrapping addRepresentation's buffer was formatted in a way prettier rejects, failing lint and blocking all six check/build jobs on the PR. * fix(presence): address review findings in the presence pipeline - stub process.platform in the glyph spec: applyMacOSMenuBarGlyphAppearance early-returns off darwin, so the suite passed locally and failed on the Windows and Linux CI runners - keep the newest call when a deferred timer is still pending: a late timer flushed the older pendingCall, leaving a stale status as the final server state - read the connection-status map with an own-property check, so a status colliding with Object.prototype ('toString') no longer yields a function - reset module state before the no-callback preload test, which was invoking the previous test's callback instead of the intended branch * feat(tray): show a distinct icon while the workspace is disconnected The icon kept painting the last known presence while disconnected, so a user who was Online saw a healthy green dot with no network. CORE-2525 asks for a variant visually distinct from Offline, which is a deliberate user choice rather than a fault. Offline is a filled disc in #9EA2A8 — the same grey as the base glyph — so a recolour cannot carry this. DisconnectedBadge is a hollow ring with a small warning tick, distinguishing the two states by shape at 16x16. The mark's amber and ring grey both sit outside the inversion window in macOSTrayGlyph, so neither is bleached to white alongside the glyph. Icon selection mirrors the menu's existing predicate, so the two cannot disagree, and keeps the no-url / unsupported / logged-out precedence and the unknown-connection startup behaviour intact. Also renders the unread badge in combined macOS assets: buildAssets passed badge for every presence-*-notification-* asset, but the component never accepted the prop, so the count was dropped. * test(presence): cover the capability check that gates old servers hasUsablePresenceApi is the decision that drives presenceSupported, and therefore whether the tray hides presence on workspaces that do not expose the module. It had no coverage: injected.ts has no spec because importing it runs heavy top-level side effects. Extracted as a pure helper injected.ts calls, so the tested code is the code that ships, with the tri-state resolved flag unchanged. * docs(qa): correct the tray presence QA pack - macOS is a shipped platform for this feature, not out of scope; the pack told testers to skip it - flow 08 probed loggedIn/supported, which are not the state field names (userLoggedIn/presenceSupported), so it could report a false result - flow 11 waited a fixed second for a server-side rate limit; now polls with a bounded timeout - flow 09 allowed a DM or a mention but only cleared mentions - flows 03/04/10: add the refresh trigger, the second-client prerequisite, and the comparison evidence the steps assumed * test(tray): name the macOS glyph spec as a main-process spec * fix(presence): require the full Presence API and make the QA pack runnable on macOS - hasUsablePresenceApi now also requires own get/stop functions, since injected.ts calls both; specs cover the missing/inherited cases - every QA flow spells out the macOS menu-bar gesture inline - flows 09/10 compare against the shipping badge asset instead of an unreachable presence-unset baseline * docs(qa): spell out the macOS menu-bar click in flows 09 and 11 * feat(tray): drop the unread count from Windows and macOS tray icons The Windows taskbar overlay and the macOS menu-bar title already show the count, so the tray only needs a badge-present variant per state. This removes the 11-way numbered matrix on win32/darwin (and the identical disconnected-notification-N copies on linux), cutting ~190 asset files. Linux keeps its numbered tray icons since it has no taskbar badge. * chore(tray): restore the untouched linux and macOS template assets The asset regeneration re-encoded files whose content did not change; keep the bytes that ship on dev. * feat(tray): draw presence as Fuselage status bullets and show presence only on Windows/macOS Presence now renders with the StatusBullet glyphs: filled circle for online, clock cut-out for away, bar cut-out for busy and a hollow ring for offline. AppIcon takes an explicit cutout so hollow shapes and cut-outs reveal the background instead of the rocket. Windows and macOS tray icons show presence only; the unread count already lives on the taskbar overlay and the menu-bar title, so the presence-*-notification assets are gone there. Linux keeps its numbered presence badges. * docs(qa): lock the tray presence pack to exact base and head refs * feat(tray): group presence under a status item with a submenu The tray menu now leads with the current presence (label + bullet icon) and opens a submenu listing Online, Away, Busy and Offline, each with its bullet and the current one checked — the same shape Teams uses. The disconnected and custom status lines stay below it. Menu bullets are 14pt assets under images/presence so the @2x representation survives. * fix(tray): size the presence menu bullets like Fuselage's StatusBullet 12pt at 1x / 24px at 2x, matching the 12px bullet Fuselage renders next to 14px text. * feat(tray): draw the disconnected badge as a filled amber bullet The grey hollow ring with a thin tick vanished at menu-bar size. The badge is now a solid amber disc with a bold exclamation mark cut out, sharing the presence bullets' footprint and cutout so the mark shows the background instead of the rocket. * feat(dev): add a Simulate Disconnected toggle to the Developer menu A non-persisted store flag that selectActiveServerPresence reads to force the active workspace's connection to 'disconnected', so the tray icon and menu can be checked without dropping the real connection. * fix(tray): match the Windows unread badge to the macOS template look With unread messages and no presence yet, macOS renders its template as a solid monochrome disc. Windows still showed the legacy red badge; it now draws the same solid disc in the rocket's grey. * refactor(tray): drop the unread fallback icons on macOS and Windows The tray shows status only on these platforms; the unread count already lives on the Windows taskbar overlay and the macOS menu-bar title. With presence unknown the icon is now the plain default, and disconnected uses a single asset on every platform since the badge ignores the count. * build(rollup): mirror src/public into app and watch it rollup-plugin-copy never deletes, so assets removed from src/public lingered in app/ (and could ship in packaged builds), and rollup did not watch src/public, so regenerated icons never triggered a rebuild. A small sync plugin now registers the assets with the watcher and purges stale files inside the mirrored subtrees. * refactor(tray): make the Linux tray status-only like macOS and Windows Linux drops its numbered unread badges; the tray now resolves to the same six states on every platform (default, four presences, disconnected). The unread count stays in the tray tooltip. * ci(pr-build): stamp the installer comments with commit, time and run The sticky comments link to fixed S3 paths that every build overwrites, so their edit date never changed and it was unclear which commit the installers came from. * ci(pr-build): show the installer build time in UTC-3 as well * docs(agents): record the tray icon rules, the simulate toggle and dev-run pitfalls * i18n(tray): translate presence status and simulate-disconnected strings en.i18n.json gained tray.presence.* and menus.simulateDisconnected for the tray presence feature; propagate them to all 21 other locales so non-English builds don't fall back to English for the new tray UI.
…3464) * feat: add isNotificationQuickReplyEnabled overridden setting Override-only key (default true) so admins can disable the notification inline reply field fleet-wide via overridden-settings.json (SUP-1097). * fix: deliver Windows notification quick replies after toast dismissal Windows auto-dismisses toasts to the Action Center in ~5s; the app then dropped the Notification instance and the webview reply handler, so any reply typed afterwards was silently lost (SUP-1097). Electron 42's Notification.handleActivation delivers those activations: - pass the notification id to the toast (Tag) and keep routing metadata past 'close' (bounded map) so late replies still reach the right room - on Windows, route reply/action/click exclusively through handleActivation (instance listeners stay as fallback when the API is unavailable) to avoid double-dispatch on live toasts - keep preload event handlers alive after toast close (bounded map) - gate hasReply on the isNotificationQuickReplyEnabled setting * fix: keep instance click listener for Windows root-toast clicks Root-toast clicks carry no activation arguments on Windows (only structured reply/action activations do), so activation routing can never receive them. Attach the instance click listener unconditionally, drop the unreachable click branch from the activation handler, and slim the routing metadata to what reply/action need. * test: provide select in setup.main.spec store mock createNotification now reads isNotificationQuickReplyEnabled via select; the coverage spec from #3429 mocked the store without it. * fix: guard clipboard patch so injection survives plain-HTTP workspaces navigator.clipboard is undefined outside secure contexts, so the unguarded writeText assignment threw and aborted injected.ts start() before the Notification shim installed — silently killing all desktop notifications on http:// workspaces. Found during real-Windows runtime validation of this PR. * chore: trigger PR build artifacts for QA * fix: keep Windows reply routing alive while the Action Center card is The web client auto-closes every desktop notification ~10s after showing it, but on Windows the Action Center card stays on screen and still accepts quick replies. Treating that close as the end of the notification's life dropped the routing metadata (and left the card in place, since the instance had already been removed on the banner's own 'close'), so any reply typed after those 10s was silently lost — which is why thread replies failed while faster DM replies worked. - retain routing metadata past dismissal on the activation-routing path - keep the timed-out instance reachable so a dismissal can actually remove the Action Center card - fall back to a broadcast dispatch when metadata is genuinely gone (LRU eviction, cold start) instead of dropping the reply * refactor: log notification activation drops through the app logger console.warn output is invisible in packaged builds, so a dropped activation left no trace anywhere a user or support could look. * docs: post-mortem for Windows notification quick replies (SUP-1097) Records the investigation across Rocket.Chat.Electron (#3464) and Rocket.Chat (#41875, #41897) and adds a Windows Notifications section to AGENTS.md with the toast lifecycle facts that drove the fix. * fix: preserve notification category for clicks on retained Windows cards close() clears notificationCategories before a root-toast click on a card still in the Action Center can read it, so the click dispatched without payload.category. Carry category in NotificationRoutingMeta as a fallback once the primary map is cleared (CodeRabbit review on #3464).
…#3472) * fix(tray): hide presence status when the active server failed to load The tray Status submenu (Online/Away/Busy/Offline) ignored server.failed and stayed fully clickable even after a navigation failure, so selecting a status silently no-oped against a broken workspace. Thread failed through selectActiveServerPresence and treat it like the existing unsupported-version case. * fix(tray): show Add workspace on the add-workspace screen even with servers buildPresenceMenuItems only offered "Add workspace" when the user had zero servers configured, so the tray hid the Status item entirely while the user was actively on the add-workspace screen with existing workspaces. Thread isAddingServer through selectActiveServerPresence and treat it the same as the no-servers case. * test(tray): assert the presence root item is absent on failed CodeRabbit review on #3472: the failed-server test only checked for top-level radio items, which would still pass if buildPresenceMenuItems ever returned a presence root item with an empty/disabled submenu instead of []. Assert via the existing findPresenceRootItem helper so the test actually fails if the failed guard stops suppressing presence controls.
…ore, Microsoft Store, Snap, Flathub) (#3460) * fix(updates): make Check for updates work on Mac App Store builds Mac App Store builds cannot use electron-updater (store policy requires updates to ship through the App Store), so the Check for updates action silently did nothing there. Route user-initiated checks on MAS builds through the unauthenticated iTunes Lookup API instead: compare the published store version against the running one and offer an Open App Store deep link when a newer version is available. All other distributions keep the existing electron-updater flow unchanged. * fix(updates): address review — dedicated App Store action and URL allowlist Opening the App Store reused UPDATES_DOWNLOAD_REQUESTED, which flips updateDownloadStatus to 'downloading' with no terminal action on the MAS path, leaving the update pill stuck at 'Updating 0%'. Route the hand-off through a dedicated UPDATES_OPEN_APP_STORE_REQUESTED action that closes the panel without touching download state. Also restrict store URLs from the lookup response to https on Apple store hosts before shell.openExternal, build a real Error for failed lookups, and exercise the lookup timeout path in the specs. * fix(updates): extend store-aware update checks to Microsoft Store, Snap, and Flathub Generalize the Mac App Store work into per-store adapters behind a single updateStore field ('mas' | 'windows' | 'snap' | 'flatpak'). Snap and Flathub get real version checks against their public catalog APIs with store-flavored open-page actions; the Microsoft Store has no unauthenticated version endpoint, so its check opens the product page directly and resets the transient checking state. Linux installs with no update path (deb/rpm/tar.gz) no longer show the dead Check for updates menu item. The dev simulation override becomes ROCKETCHAT_SIMULATE_STORE=<store>. * fix(updates): address review minors — spec placement, menu gate, error payload Move the store-update specs under src/updates/main/ so the main-process Jest project actually discovers them (the renderer project was picking them up — testMatch keys on the main/ directory, not the file suffix). Gate the Check for updates menu items on isUpdatingEnabled as well, so admin-disabled builds don't expose a click that nothing settles. Route the store lookup failure through dispatchUpdateError so the custom error name survives the structured clone to renderers, and strengthen the progress-reducer regression test with a nonzero seed. * test(settings): cover store-update bypass in CheckForUpdates Mirrors UpdateLabel.spec.tsx's isUpdatingAllowed/isStoreUpdate coverage for the settings-window equivalent, ported from the deleted AboutDialog.
* fix: keep macOS native fullscreen when pressing ESC On macOS, Electron hands every key event the web content did not consume back to the native window (`WebContents::PlatformHandleKeyboardEvent` calls `[ns_event.window redispatchKeyEvent:]`). AppKit answers ESC with `cancelOperation:`, which leaves native fullscreen. So pressing ESC while a video is in HTML5 fullscreen — or with nothing at all to dismiss — also dropped the app out of fullscreen. Chrome never returns the raw NSEvent to AppKit and therefore stays fullscreen. The previous guard only stopped forwarding ESC through `sendInputEvent`, which produces a synthetic event that can never reach AppKit, so the redispatch was left untouched. Swallow the ESC key down in `before-input-event`, which runs before the renderer and thus before the redispatch, and replay it with `sendInputEvent`: the replay still reaches the page but carries no NSEvent. While the guest is in HTML5 fullscreen the key is consumed and the fullscreen exit is driven explicitly, so ESC leaves only the video fullscreen, like Chrome. The guard is applied to both the server views and the root window contents, and is inert outside macOS. Also stop forwarding the ESC key up to the root window: only key down is consumed there, and the key up leaked an unguarded ESC once the guest had left HTML5 fullscreen. * test: cover the macOS Escape fullscreen guard Add regression coverage for the Escape handling that keeps the window in macOS native fullscreen. Automated coverage, in `src/ui/main/rootWindow.spec.ts`, asserts that the `before-input-event` listener is installed on the root window contents, that an Escape key down in fullscreen is cancelled and replayed, that the replay passes through, and that the handler is inert when the window is not fullscreen or the platform is not macOS. QA coverage, in `qa/macos-fullscreen-escape/`, covers the half no Jest test can reach: the bug depends on how AppKit reacts to a real key event from the operating system, and any test generated key event is synthetic and never gets there, which is what let the earlier regression pass CI. The pack covers video HTML5 fullscreen for both an attachment and an embedded cross-origin player, plain Escape with nothing to dismiss, the Escape/menu-shortcut/fullscreen-toggle regressions, and a Windows and Linux unchanged-behavior check. Also restrict the HTML5 fullscreen Escape branch in the server view to macOS. It was running on every platform, which changed Windows and Linux behavior for a bug that only exists on macOS; those platforms keep leaving the fullscreen exit to Chromium and keep the original no-forward guard. * fix: harden the Escape fullscreen guard replay bookkeeping - swallow auto-repeated ESC keydowns instead of replaying them, so a held ESC can never race an in-flight replay and leak to AppKit (also dedupes the per-repeat exitFullscreen executeJavaScript calls) - consume the replay credit before the fullscreen gate, so a replay landing after fullscreen ended clears the credit instead of leaving it armed for the next real press - collapse the pending-replay counter + expiry pair into a single expiry timestamp (the counter could never exceed 1) - extract isWindowInAnyFullscreen so serverView and rootWindow share one fullscreen predicate; restore the typed createInput fixture - bail the guest-to-root ESC forward when the root window or its webContents is destroyed - rewrite the serverView Escape branch comment that described the opposite of the code; keep the guard debug log in packaged builds - cover auto-repeat swallow, non-fullscreen pass-through and stale-credit clearing with new specs * ci: trigger pull request artifact build --------- 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
…3431) * fix(updates): install macOS updates through electron-updater (#955) The darwin branch of the `update-downloaded` handler destroyed every window and then drove Electron's native `autoUpdater` directly: nativeUpdater.checkForUpdates(); nativeUpdater.on('update-downloaded', nativeUpdateDownloadedCallback); electron-updater's `MacUpdater` already owns that same native `autoUpdater` singleton. It serves the downloaded zip from a local authenticated proxy server, calls `setFeedURL` against it, and installs via its own `quitAndInstall()` — the only path that consults `squirrelDownloadedUpdate` and reaches `handleUpdateDownloaded()`. Calling `checkForUpdates()` on the singleton bypassed that bookkeeping, so Squirrel never installed: the app quit with no windows left and offered the same update on the next launch. The listener was also registered after the call meant to trigger it, and the `try/catch` wrapped only the `setImmediate` scheduling rather than its body, so a throw from the later tick escaped and never dispatched `UPDATES_ERROR_THROWN` — leaving the failure silent. Drop the darwin special case and call `autoUpdater.quitAndInstall(true, true)` on every platform, restoring the behaviour that predated 972f141, and move the `try/catch` inside the callback so install failures are reported. Not verified against a live update feed: that needs a packaged, signed macOS build. `tsc --noEmit`, `eslint`, and the full test suite pass. * fix(updates): restore quit handler and normalize errors on failed install Addresses two review findings on the install path: - `app.removeAllListeners('window-all-closed')` detaches the app's quit handler process-wide. The removal is needed so the pending quit isn't cancelled, but when the install throws we never quit, leaving those listeners detached for the rest of the session — closing every window would then no longer reach the handler in src/app/main/app.ts. Capture the listeners first and reattach them in the error path. - `error instanceof Error && dispatch(...)` silently dropped non-Error thrown values, so a thrown string or object produced no error action at all. Since this block exists specifically to surface install failures that used to go unreported, normalize any thrown value into a serializable {message, stack, name} payload before dispatching.
Contributor
|
Important Review skippedToo many files! This PR contains 327 files, which is 177 over the limit of 150. To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (69)
📒 Files selected for processing (327)
You can disable this status message by setting the 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes dev (4.17.0) to master per docs/release-process.md. Merge with a TRUE merge commit (
--merge), never squash. Full changelog in #3482.