Skip to content

Release 4.17.0 - #3483

Merged
jeanfbrito merged 17 commits into
masterfrom
dev
Aug 31, 2026
Merged

Release 4.17.0#3483
jeanfbrito merged 17 commits into
masterfrom
dev

Conversation

@jeanfbrito

Copy link
Copy Markdown
Member

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.

rodrigok and others added 17 commits August 13, 2026 11:32
… 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.
@jeanfbrito
jeanfbrito merged commit 3e0df5d into master Aug 31, 2026
9 checks passed
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too 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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 154bfc84-f6df-45c1-b24c-af706ab9644f

📥 Commits

Reviewing files that changed from the base of the PR and between 4667150 and 729869a.

⛔ Files ignored due to path filters (69)
  • src/public/images/tray/darwin/notificationTemplate.png is excluded by !**/*.png
  • src/public/images/tray/darwin/notificationTemplate@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-1.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-1@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-2.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-2@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-3.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-3@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-4.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-4@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-5.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-5@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-6.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-6@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-7.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-7@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-8.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-8@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-9.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-9@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-dot.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-dot@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-plus-9.png is excluded by !**/*.png
  • src/public/images/tray/linux/notification-plus-9@2x.png is excluded by !**/*.png
  • src/public/images/tray/win32/notification-1.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-2.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-3.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-4.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-5.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-6.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-7.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-8.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-9.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-dot.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/notification-plus-9.ico is excluded by !**/*.ico
  • qa/core-2525-tray-presence/exports/qase-import.csv is excluded by !**/*.csv
  • src/public/images/presence/away.png is excluded by !**/*.png
  • src/public/images/presence/away@2x.png is excluded by !**/*.png
  • src/public/images/presence/busy.png is excluded by !**/*.png
  • src/public/images/presence/busy@2x.png is excluded by !**/*.png
  • src/public/images/presence/offline.png is excluded by !**/*.png
  • src/public/images/presence/offline@2x.png is excluded by !**/*.png
  • src/public/images/presence/online.png is excluded by !**/*.png
  • src/public/images/presence/online@2x.png is excluded by !**/*.png
  • src/public/images/tray/darwin/disconnected.png is excluded by !**/*.png
  • src/public/images/tray/darwin/disconnected@2x.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-away.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-away@2x.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-busy.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-busy@2x.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-offline.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-offline@2x.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-online.png is excluded by !**/*.png
  • src/public/images/tray/darwin/presence-online@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/disconnected.png is excluded by !**/*.png
  • src/public/images/tray/linux/disconnected@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-away.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-away@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-busy.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-busy@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-offline.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-offline@2x.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-online.png is excluded by !**/*.png
  • src/public/images/tray/linux/presence-online@2x.png is excluded by !**/*.png
  • src/public/images/tray/win32/disconnected.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/presence-away.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/presence-busy.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/presence-offline.ico is excluded by !**/*.ico
  • src/public/images/tray/win32/presence-online.ico is excluded by !**/*.ico
📒 Files selected for processing (327)
  • .claude/skills/ship-release/SKILL.md
  • .github/workflows/pull-request-build.yml
  • AGENTS.md
  • README.md
  • docs/COVERAGE.md
  • docs/KNOWN_ISSUES.md
  • docs/release-process.md
  • electron-builder.json
  • jest.config.js
  • package.json
  • rollup.config.mjs
  • skills/dev-app-verify/SKILL.md
  • src/app/PersistableValues.ts
  • src/app/actions.ts
  • src/app/main/app.ts
  • src/app/main/data.spec.ts
  • src/app/main/data.ts
  • src/app/main/persistence.ts
  • src/app/selectors.ts
  • src/buildAssets.ts
  • src/deepLinks/main.spec.ts
  • src/deepLinks/main.ts
  • src/downloads/actions.spec.ts
  • src/downloads/integration.spec.ts
  • src/downloads/main.spec.ts
  • src/downloads/main.ts
  • src/downloads/notifications.spec.ts
  • src/i18n/ar.i18n.json
  • src/i18n/de-DE.i18n.json
  • src/i18n/en.i18n.json
  • src/i18n/es.i18n.json
  • src/i18n/fi.i18n.json
  • src/i18n/fr.i18n.json
  • src/i18n/hu.i18n.json
  • src/i18n/it-IT.i18n.json
  • src/i18n/ja.i18n.json
  • src/i18n/nb-NO.i18n.json
  • src/i18n/nn.i18n.json
  • src/i18n/no.i18n.json
  • src/i18n/pl.i18n.json
  • src/i18n/pt-BR.i18n.json
  • src/i18n/ru.i18n.json
  • src/i18n/se.i18n.json
  • src/i18n/sv.i18n.json
  • src/i18n/tr-TR.i18n.json
  • src/i18n/uk-UA.i18n.json
  • src/i18n/zh-CN.i18n.json
  • src/i18n/zh-TW.i18n.json
  • src/i18n/zh.i18n.json
  • src/injected.ts
  • src/ipc/channels.ts
  • src/logViewerWindow/LogEntry.tsx
  • src/logViewerWindow/__tests__/types.spec.ts
  • src/logViewerWindow/constants.ts
  • src/logViewerWindow/ipc.ts
  • src/logViewerWindow/log-viewer-window.tsx
  • src/logViewerWindow/logViewerWindow.tsx
  • src/logViewerWindow/types.ts
  • src/logging/cleanup.spec.ts
  • src/logging/fallback.spec.ts
  • src/logging/scopes.spec.ts
  • src/logging/utils.spec.ts
  • src/main.ts
  • src/notifications/main.ts
  • src/notifications/preload.ts
  • src/preload.ts
  • src/public/log-viewer-window.html
  • src/servers/common.spec.ts
  • src/servers/common.ts
  • src/servers/main.spec.ts
  • src/servers/preload/api.ts
  • src/servers/reducers.ts
  • src/store/rootReducer.ts
  • src/ui/__tests__/selectors.spec.ts
  • src/ui/actions.ts
  • src/ui/components/AboutDialog/index.tsx
  • src/ui/components/DownloadsManagerView/index.tsx
  • src/ui/components/ServersView/ErrorView.tsx
  • src/ui/components/ServersView/MarkdownContent.tsx
  • src/ui/components/ServersView/PdfContent.tsx
  • src/ui/components/ServersView/ServerPane.tsx
  • src/ui/components/ServersView/index.tsx
  • src/ui/components/SettingsView/features/ClearPermittedScreenCaptureServers.tsx
  • src/ui/components/SettingsView/features/DebugLogging.tsx
  • src/ui/components/SettingsView/features/DetailedEventsLogging.tsx
  • src/ui/components/SettingsView/features/E2ePdfPreviewSizeLimit.tsx
  • src/ui/components/SettingsView/features/NavigationLayout.tsx
  • src/ui/components/SettingsView/features/Telephony.tsx
  • src/ui/components/SettingsView/features/TelephonyGlobalShortcut.spec.tsx
  • src/ui/components/SettingsView/features/TelephonyGlobalShortcut.tsx
  • src/ui/components/SettingsView/features/ThemeAppearance.tsx
  • src/ui/components/SettingsView/features/VerboseOutlookLogging.tsx
  • src/ui/components/Shell/index.spec.tsx
  • src/ui/components/Shell/index.tsx
  • src/ui/components/TabBar/styles.tsx
  • src/ui/components/TopBar/DownloadsIndicator.spec.tsx
  • src/ui/components/TopBar/DownloadsIndicator.tsx
  • src/ui/components/TopBar/DownloadsIndicatorItem.tsx
  • src/ui/components/TopBar/UpdateLabel.spec.tsx
  • src/ui/components/TopBar/UpdateLabel.tsx
  • src/ui/icons/AppIcon.tsx
  • src/ui/icons/LinuxTrayIcon.tsx
  • src/ui/icons/MacOSTrayIcon.tsx
  • src/ui/icons/WindowsTrayIcon.tsx
  • src/ui/main/icons.spec.ts
  • src/ui/main/icons.ts
  • src/ui/main/menuBar.spec.ts
  • src/ui/main/menuBar.ts
  • src/ui/main/trayIcon.ts
  • src/ui/reducers/__tests__/stateGroups.spec.ts
  • src/ui/reducers/currentView.ts
  • src/ui/reducers/openDialog.ts
  • src/ui/selectors.ts
  • src/updates/actions.ts
  • src/updates/common.ts
  • src/updates/main.spec.ts
  • src/updates/main.ts
  • src/updates/reducers.ts
  • src/updates/reducers/__tests__/updates.spec.ts
  • src/videoCallWindow/main/ipc.main.spec.ts
  • src/videoCallWindow/video-call-window.ts
  • docs/postmortem-notification-quick-reply-sup-1097.md
  • qa/core-2525-tray-presence/README.md
  • qa/core-2525-tray-presence/flows/01-presence-icon-states.md
  • qa/core-2525-tray-presence/flows/02-presence-menu-radios.md
  • qa/core-2525-tray-presence/flows/03-custom-status-text.md
  • qa/core-2525-tray-presence/flows/04-active-workspace-scoping.md
  • qa/core-2525-tray-presence/flows/05-disconnected-state.md
  • qa/core-2525-tray-presence/flows/06-logged-out-state.md
  • qa/core-2525-tray-presence/flows/07-no-workspace-state.md
  • qa/core-2525-tray-presence/flows/08-unsupported-workspace.md
  • qa/core-2525-tray-presence/flows/09-unread-badge-regression.md
  • qa/core-2525-tray-presence/flows/10-badge-and-presence-combined.md
  • qa/core-2525-tray-presence/flows/11-rate-limit-rapid-clicks.md
  • src/app/main/buildAssets.main.spec.ts
  • src/app/main/mainEntry.main.spec.ts
  • src/app/main/persistence.main.spec.ts
  • src/documentViewer/main/ipc.main.spec.ts
  • src/documentViewerWindow/DocumentViewerWindow.tsx
  • src/documentViewerWindow/constants.ts
  • src/documentViewerWindow/document-viewer-window.tsx
  • src/documentViewerWindow/document.ts
  • src/documentViewerWindow/ipc.ts
  • src/documentViewerWindow/main/ipc.main.spec.ts
  • src/documentViewerWindow/saveDocument.ts
  • src/downloads/__tests__/actions.spec.ts
  • src/downloads/__tests__/notifications.spec.ts
  • src/downloads/main/integration.main.spec.ts
  • src/downloadsWindow/DownloadRow.tsx
  • src/downloadsWindow/DownloadsSidebar.tsx
  • src/downloadsWindow/DownloadsStatusBar.tsx
  • src/downloadsWindow/DownloadsToolbar.tsx
  • src/downloadsWindow/DownloadsWindow.tsx
  • src/downloadsWindow/FileTypeIcon.tsx
  • src/downloadsWindow/__tests__/DownloadsWindow.spec.tsx
  • src/downloadsWindow/__tests__/fileTypes.spec.ts
  • src/downloadsWindow/__tests__/grouping.spec.ts
  • src/downloadsWindow/constants.ts
  • src/downloadsWindow/downloads-window.tsx
  • src/downloadsWindow/fileTypes.ts
  • src/downloadsWindow/grouping.ts
  • src/downloadsWindow/ipc.ts
  • src/downloadsWindow/main/ipc.main.spec.ts
  • src/downloadsWindow/styles.tsx
  • src/i18n/__tests__/renderer.spec.ts
  • src/i18n/__tests__/resources.spec.ts
  • src/logViewerWindow/LogStatusBar.tsx
  • src/logViewerWindow/LogTimeline.tsx
  • src/logViewerWindow/LogViewerSidebar.tsx
  • src/logViewerWindow/LogViewerToolbar.tsx
  • src/logViewerWindow/TimelineBar.tsx
  • src/logViewerWindow/__tests__/LogEntry.spec.tsx
  • src/logViewerWindow/__tests__/LogTimeline.spec.tsx
  • src/logViewerWindow/__tests__/displayControls.spec.tsx
  • src/logViewerWindow/__tests__/ipc.spec.ts
  • src/logViewerWindow/__tests__/logFormatters.spec.ts
  • src/logViewerWindow/__tests__/logViewerWindow.spec.tsx
  • src/logViewerWindow/__tests__/pagination.spec.ts
  • src/logViewerWindow/__tests__/parseLogs.spec.ts
  • src/logViewerWindow/__tests__/textHighlight.spec.ts
  • src/logViewerWindow/__tests__/timeline.spec.ts
  • src/logViewerWindow/appearance.ts
  • src/logViewerWindow/logFormatters.ts
  • src/logViewerWindow/main/ipc.main.spec.ts
  • src/logViewerWindow/pagination.ts
  • src/logViewerWindow/parseLogs.ts
  • src/logViewerWindow/styles.tsx
  • src/logViewerWindow/textHighlight.ts
  • src/logViewerWindow/timeline.ts
  • src/logging/__tests__/cleanup.spec.ts
  • src/logging/__tests__/fallback.spec.ts
  • src/logging/__tests__/scopes.spec.ts
  • src/logging/__tests__/utils.spec.ts
  • src/notifications/__tests__/attentionDrawing.spec.ts
  • src/notifications/__tests__/parseActivationArguments.spec.ts
  • src/notifications/main.spec.ts
  • src/notifications/main/setup.main.spec.ts
  • src/notifications/parseActivationArguments.ts
  • src/notifications/reducers/isNotificationQuickReplyEnabled.ts
  • src/outlookCalendar/main/ipc.main.spec.ts
  • src/outlookCalendar/reducers/__tests__/outlookReducers.spec.ts
  • src/public/document-viewer-window.html
  • src/public/downloads-window.html
  • src/public/settings-window.html
  • src/screenSharing/__tests__/resolveStandaloneOriginWindow.main.spec.ts
  • src/screenSharing/__tests__/screenSharePicker.spec.tsx
  • src/servers/__tests__/common.spec.ts
  • src/servers/__tests__/fetchInfo.spec.ts
  • src/servers/main/preloadCoverage.main.spec.ts
  • src/servers/main/resolveServerUrl.main.spec.ts
  • src/servers/main/setupServers.main.spec.ts
  • src/servers/preload/__tests__/presence.spec.ts
  • src/servers/preload/__tests__/presenceDebounce.spec.ts
  • src/servers/preload/__tests__/presenceSnapshot.spec.ts
  • src/servers/preload/__tests__/presenceSupport.spec.ts
  • src/servers/preload/presence.ts
  • src/servers/preload/presenceDebounce.ts
  • src/servers/preload/presenceSnapshot.ts
  • src/servers/preload/presenceSupport.ts
  • src/servers/reducers/__tests__/presence.spec.ts
  • src/settingsWindow/SettingsSidebar.tsx
  • src/settingsWindow/SettingsWindow.tsx
  • src/settingsWindow/__tests__/SettingsSidebar.spec.tsx
  • src/settingsWindow/__tests__/SettingsWindow.spec.tsx
  • src/settingsWindow/__tests__/fuzzy.spec.ts
  • src/settingsWindow/__tests__/searchIndex.spec.ts
  • src/settingsWindow/__tests__/sections.spec.ts
  • src/settingsWindow/constants.ts
  • src/settingsWindow/fuzzy.ts
  • src/settingsWindow/ipc.ts
  • src/settingsWindow/main/ipc.main.spec.ts
  • src/settingsWindow/searchIndex.ts
  • src/settingsWindow/sections.ts
  • src/settingsWindow/sections/AdvancedSection.tsx
  • src/settingsWindow/sections/AppearanceSection.tsx
  • src/settingsWindow/sections/CertificateRow.spec.tsx
  • src/settingsWindow/sections/CertificateRow.tsx
  • src/settingsWindow/sections/CertificatesSection.tsx
  • src/settingsWindow/sections/GeneralSection.tsx
  • src/settingsWindow/sections/TelephonySection.tsx
  • src/settingsWindow/sections/VideoCallsSection.tsx
  • src/settingsWindow/settings-window.tsx
  • src/ui/components/App.spec.tsx
  • src/ui/components/CertificatesManager/CertificatesManager.spec.tsx
  • src/ui/components/FailureImage.spec.tsx
  • src/ui/components/SelectClientCertificateDialog/index.spec.tsx
  • src/ui/components/ServerInfoContent.spec.tsx
  • src/ui/components/ServerInfoModal/index.spec.tsx
  • src/ui/components/ServersView/DocumentViewer.spec.tsx
  • src/ui/components/ServersView/ErrorView.spec.tsx
  • src/ui/components/ServersView/MarkdownContent.spec.tsx
  • src/ui/components/ServersView/PdfContent.spec.tsx
  • src/ui/components/ServersView/ServerPane.spec.tsx
  • src/ui/components/SettingsView/SettingsView.spec.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.spec.tsx
  • src/ui/components/SettingsView/features/CheckForUpdates.tsx
  • src/ui/components/SettingsView/features/ChromeThumbnailOption.tsx
  • src/ui/components/SettingsView/features/ClearPermittedScreenCaptureServers.spec.tsx
  • src/ui/components/SettingsView/features/MenuBar.spec.tsx
  • src/ui/components/SettingsView/features/MinimizeOnClose.spec.tsx
  • src/ui/components/SettingsView/features/SettingGroupDivider.tsx
  • src/ui/components/SettingsView/features/ToggleField.spec.tsx
  • src/ui/components/SettingsView/features/UpdateChannel.tsx
  • src/ui/components/SettingsView/features/WindowChromeThumbnail.spec.tsx
  • src/ui/components/SettingsView/features/WindowChromeThumbnail.tsx
  • src/ui/components/SettingsView/features/moreSettings.spec.tsx
  • src/ui/components/SettingsView/features/settingsToggles.spec.tsx
  • src/ui/components/SettingsView/features/thumbnailMetrics.ts
  • src/ui/components/SettingsView/tabs.spec.tsx
  • src/ui/components/TopBar/index.spec.tsx
  • src/ui/components/utils/ErrorCatcher.spec.tsx
  • src/ui/components/utils/TooltipProvider.spec.tsx
  • src/ui/components/utils/createAnchor.renderer.spec.ts
  • src/ui/components/utils/getServerDomId.spec.ts
  • src/ui/components/utils/getServerInitials.spec.ts
  • src/ui/icons/DisconnectedBadge.tsx
  • src/ui/icons/PresenceBullet.tsx
  • src/ui/icons/PresenceBulletCutout.tsx
  • src/ui/icons/PresenceMenuIcon.tsx
  • src/ui/icons/presenceColors.ts
  • src/ui/main/__tests__/rootWindowGeometry.main.spec.ts
  • src/ui/main/dock.main.spec.ts
  • src/ui/main/macOSTrayGlyph.main.spec.ts
  • src/ui/main/macOSTrayGlyph.ts
  • src/ui/main/menuBar.main.spec.ts
  • src/ui/main/secondaryWindowControls.ts
  • src/ui/main/secondaryWindowFocus.ts
  • src/ui/main/secondaryWindowState.main.spec.ts
  • src/ui/main/secondaryWindowState.ts
  • src/ui/main/touchBar.main.spec.ts
  • src/ui/main/trayIcon.main.spec.ts
  • src/ui/main/trayIconMenu.main.spec.ts
  • src/ui/reducers/isDownloadsWindowOpen.ts
  • src/ui/reducers/isLogViewerWindowOpen.spec.ts
  • src/ui/reducers/isLogViewerWindowOpen.ts
  • src/ui/reducers/isPresenceDisconnectionSimulated.ts
  • src/ui/reducers/isSettingsWindowOpen.ts
  • src/ui/reducers/secondaryWindowStates.ts
  • src/ui/windowChrome/DayHeader.tsx
  • src/ui/windowChrome/FilterRow.tsx
  • src/ui/windowChrome/FilterSection.tsx
  • src/ui/windowChrome/NavRow.tsx
  • src/ui/windowChrome/SectionLabel.tsx
  • src/ui/windowChrome/SidebarPlaceholder.tsx
  • src/ui/windowChrome/StatusBar.tsx
  • src/ui/windowChrome/StatusItem.tsx
  • src/ui/windowChrome/TextButton.tsx
  • src/ui/windowChrome/WindowControls.tsx
  • src/ui/windowChrome/WindowToolbar.tsx
  • src/ui/windowChrome/__tests__/appearance.spec.ts
  • src/ui/windowChrome/__tests__/copiedFeedback.spec.tsx
  • src/ui/windowChrome/__tests__/filters.spec.ts
  • src/ui/windowChrome/appearance.ts
  • src/ui/windowChrome/channels.ts
  • src/ui/windowChrome/filters.ts
  • src/ui/windowChrome/styles.tsx
  • src/ui/windowChrome/useCopiedFeedback.ts
  • src/ui/windowChrome/useFindShortcut.ts
  • src/ui/windowChrome/useTransparency.ts
  • src/updates/main/detectUpdateStore.main.spec.ts
  • src/updates/main/setupUpdates.main.spec.ts
  • src/updates/main/storeUpdates.main.spec.ts
  • src/updates/storeUpdates.ts
  • src/userPresence/main/setup.main.spec.ts
  • src/videoCallWindow/__tests__/screenSharePickerMount.spec.ts
  • src/videoCallWindow/__tests__/validateVideoCallUrl.spec.ts
  • src/videoCallWindow/validateVideoCallUrl.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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

❤️ Share

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants