Skip to content

CoreTrace Desktop 6.0: the Tauri application - #79

Merged
cedricroulof merged 67 commits into
masterfrom
feat/tauri-react-desktop
Sep 18, 2026
Merged

cedricroulof merged 67 commits into
masterfrom
feat/tauri-react-desktop

Conversation

@cedricroulof

Copy link
Copy Markdown
Collaborator

The 6.0 line, released as v6.0.0-beta.2: the desktop rewritten on Tauri and React, with cloud and local analysis, the tool-failure report, session persistence, and the release workflow that publishes installers on every tag.

master's ten Electron-era commits (#76–#78) are merged in; the branch never touched those files, so they come through as on master, normalised to LF.

Release notes: docs/releases/v6.0.0-beta.2.md.

Users read an eight-letter code off the desktop and typed it into the browser.
The control plane returns the code alongside the verification URI, so the link
can carry it.

Append it as user_code, after the same external-URL check the bare URI already
went through.
An analysis needed the tool configuration and the compilation database picked by
hand, and a run without a database lost the compiler options and include paths.
The cloud runner already solves this: its stack-analyzer adapter looks for a
compile_commands.json near the sources and writes a minimal one when there is
none.

Do the same on the desktop. Selecting an analyser picks up the packaged
tool-config.json next to a CoreTrace distribution, and a run without a chosen
database searches the workspace for one that actually covers the active file,
falling back to a generated entry with clang and the workspace's include
directory.
The account card showed the principal's name and fell back to "Non connecté"
when it was empty. The platform names API keys only, so every signed-in human
has no name and read as disconnected while their e-mail and the sign-out button
sat right beside the message.

Show the e-mail the session already carries, keep the disconnected wording for
an absent session, and put the organisations underneath.
native/target is 12 GB of rebuildable Rust output sitting untracked next to the
spike sources. Any `git add -A` would have tried to commit it. The sources stay
visible, so what to do with them is still a decision rather than a default.
Opening a folder offers only the local analysis, which is why the application
insists on being pointed at a ctrace executable first. The platform's upload and
job endpoints exist and ctrace-cloud already uses them; the desktop reaches the
cloud only through a repository connected on GitHub, so a folder on disk has no
path there at all. The Electron application had this and the Tauri rewrite did
not carry it over.

Record the design: pack the workspace filtered, upload it, show the quote, spend
nothing until it is confirmed, and report the findings beside the local ones.
The desktop could reach the cloud only through a repository already connected on
the web, and had nowhere to connect GitHub at all.

Add a pill in Settings carrying GitHub's mark: it asks the platform to start the
authorisation, opens it in the system browser, and polls the identity until it
appears, so the user does not have to come back and refresh. GitHub's consent
screen cannot be hosted here, and the platform binds the flow to the signed-in
session, so the browser is where it has to finish.

Once connected the pill says so rather than offering the same action again.
Ignoring native/ whole, since `git add -A` just swept its 88 files into this.
…goes

Choosing a folder for every clone was a decision the user had no basis to make,
and the dialog led with it. Clones now go under the application's data
directory, and Settings says where, so what was just cloned can still be found.

Repositories sit under their owner: two accounts with a repository of the same
name would otherwise land in the same folder. Cloning something already there
opens it rather than refusing, which is what asking to clone it again means.
The platform reads tar through zstd and nothing else, and it is told the digest
and the size before the bytes are sent. Both come out of the same pass that
writes the file, so a large workspace is never held in memory and the two
figures cannot disagree with what is on disk.

Directories an analysis never reads are skipped, symbolic links are not
followed so a link cannot send files outside the workspace, and the walk is
iterative because tree depth belongs to the user's project, not to our stack.
The desktop could reach the cloud only through a repository connected on
GitHub, so a folder on disk had no path there at all. The Electron application
had this and the Tauri rewrite dropped it.

Pack the workspace, authorise an upload, send it to the presigned URL, wait for
the platform to verify it, and create a job — without auto_confirm, so the run
parks on its quote and nothing is spent until the user approves it.

An archive larger than the plan allows is refused before it travels rather than
after, a rejected upload is reported in the platform's own words, and the
temporary archive goes when the run ends however it ends.
…accepted

The panel sits above the analyses list whenever a folder is open and an
organisation is selected. It reports each phase, shows the quoted CTU beside a
separate approval, and offers cancellation throughout — including at the quote,
where cancelling costs nothing at all.
A finding the platform produced and one found on this machine looked identical,
so the reader could not tell which had run. Carry the origin through the parser
and mark the cloud ones in the list.
…p showing a frozen spinner

Analysing a commit that already has an analysis answered "Conflict (HTTP 409)".
The platform names the job that covers it in the problem's detail; the desktop
threw that away, so the reader was told no and given nowhere to go. Open that
analysis instead, and say it can be run again.

The spinner was frozen because Windows animations are off, which turns on
prefers-reduced-motion and disables every animation. A circle that cannot turn
reads as a hung application, so it is now hidden and the words beside it carry
the meaning alone. The cloud panel's own spinners were missing the class that
turns them at all.
The upload poll waited for "ready" and the job poll for "succeeded". Neither
exists. The platform's uploads are authorized, completed, verified, rejected or
expired, and its jobs end at completed, rejected or cancelled — "failed" is a
conclusion, not a status.

So an upload the platform verified in three seconds sat as "Envoi de 22
fichiers" for two minutes and then failed, and a confirmed run would have polled
for half an hour after the user had already paid. The tests now pin the real
names, and an expired upload says so instead of waiting out the loop.

Verification also gets its own phase: the bytes have gone by then, and saying
"sending" through it reads as a stalled upload.
The quote read zero because a job is created before it is priced: the meter
runs, then the job parks at awaiting_confirmation carrying its cost. Reading it
off the creation answer invited approval of a real charge shown as nothing.
Wait for the price, and say "Calcul du coût…" while it is worked out.

Past analyses read zero for a different reason: the listing carries no runs by
design, and the cost was summed from them. Take it from the job instead.
The panel said "Résultats ci-dessous" and did nothing, so a finished analysis
left an empty page below it and a history that still had not moved.

Finishing now refreshes the list and opens that analysis, once per run rather
than on every poll.
… readable

Cloning failed with "could not create work tree dir ... Invalid argument":
canonicalize returns a Windows extended-length path and git will not create a
work tree at one. The prefix now comes off before any path reaches a command.

The findings heading counted every finding while the list showed the filtered
ones, so searching left a number describing nothing on screen. Levels are a
letter in a coloured mark rather than the words "error" and "warning", and a
report still arriving shows placeholders: an empty list read as "no findings",
which is a different and wrong answer.

The editor offers the cloud run beside the local one, and the clone dialog
suggests the repositories already connected, since its field looked like a
search that did nothing.
A project is often more than one directory — a library beside the application
that uses it — and opening the second closed the first. The state holds every
open folder now, each reachable by its own id, and the breadcrumb switches
between them, adds another and closes one. Opening a folder already open is the
same folder rather than a second copy.

A cloud run also sends the folder's name, so a history of uploads says what each
one was instead of "Analyse cloud" however many different projects it holds.
Switching folders remounts the editor, so returning to one lost every tab.

Each folder now remembers which files were open and which was in front, and
reopens them on return. Only the paths are kept: switching discards unsaved
work by design, so drafts are read from disk rather than resurrected, and a
file deleted since simply does not come back. Closing a folder forgets them.
…face

Opening a past analysis fetched a signed link and then downloaded the report,
holding the session for both — so a job's reports could not overlap, and every
other call the application made queued behind them. Reports are about 20 KB;
the wait was round trips, serialised, not data.

The download needs no session state, so the session is released before it. A
finished run's report is also immutable, so it is fetched once rather than on
every visit.

A local analysis result is now discarded when the reader has moved to another
folder since it started: the closure captured the folder as it was, and an
analysis outlives that, so the answer arrived in a folder it did not belong to.
A tool can finish having written no report; the platform answers 404 and the
desktop showed it in red as "Not found", which is a lie about a run that worked.
Nothing to say is now no findings.

The history titled every upload "Sources importées" because the folder's name
went into the subtitle's fallback rather than the title, and the filter could
not see it either.

Hiding the spinner under reduced motion left nothing at all to say the
application was working — worse than a still icon, since a slow operation and a
dead one looked the same. The icon stays, motionless, the words carry the
meaning, and a local analysis raises the same banner a clone does.

The explorer shows every open folder rather than only the one being edited, and
opening a file in another brings that folder forward with the file open.

Also: a search over the repositories, "un dossier de cette machine" for what
that action does, and the four status names that were missing from the French
map and fell through to the platform's English.
Animations are switched off system-wide, so no spinner can turn and a still one
says nothing. Every wait now shows something that genuinely changes: the files
and bytes as the archive is packed, and the seconds elapsed, in the cloud panel
and in the banner a clone and a local analysis raise. Cloning also reports
inside its dialog, which covered the banner.

Findings are grouped by severity, so a long list opens with its errors rather
than in the order the tools happened to emit them, and the repository count sits
at the far end of the search field instead of against the placeholder.
Choosing "Dans le cloud" in the editor started the run and then showed
nothing: the panel that reports packing, upload, quoting and execution lives
in the Analyses tab, so from the editor the button appeared to do nothing for
as long as the platform took to price the run, and again for as long as the
analysis ran.

A notice now follows the run wherever the reader is, with the same line the
panel shows: a count, a size or the clock, something that changes, because
animations may be off system-wide and a line that changes is the only proof
that anything is happening. It carries Annuler, and the editor's button reads
busy for the duration. A local run reports itself the same way outside the
Analyses tab, where its own strip already does.

The line is built once and shared by the panel and the notice, so both say
the same thing.
It repeated the name of the folder the editor already shows in its tab and its
own heading, and its only action, opening the workspace page, is one click
away in the navigation above it. Its unsaved-changes dot went with it: the
editor marks a dirty tab itself.
The stylesheet had about a hundred distinct greys, hand-picked one hex step
apart, and six tokens in :root that almost nothing used. Adjacent panels and
borders never quite matched, and every new component chose a new grey.

Thirteen tokens now: surfaces stepping up from the page, lines stepping up
from the surface they sit on, text stepping down from full ink. Each literal
was mapped to the nearest by role and luminance, so nothing visibly moves;
the difference is that it can now be tuned in one place.
Body text sat at 12px, secondary text at 11 and 10, and two labels at 9 and
8px: caption sizes doing a page's reading. Every size steps up one: 14 for
body, 13 for secondary, 12 for captions, 11 for eyebrows and nothing below.
Ten radii between 2 and 14px sat side by side. Three now — 4 for controls,
8 for cards, 12 for dialogs — and the pill.
The shell, after the audit (H1–H5, N1, T2, A1):

One "Nouvelle analyse". The sidebar button, the home card and the Analyses
page all start the same launch: cloud when there is an organisation to bill,
this machine otherwise. Until now the first two only navigated to the third.
The decision is a function of its own, launchAnalysis, with its tests. Writing
it exposed a fault in this morning's version: the cloud run's actions catch
and report their own errors and never throw, so the "fall back when the
platform refuses" branch could not fire. They now resolve to whether the
platform accepted, and the fallback is reachable.

One status bar. A run reported itself in three places — a strip above the
page, a notice inside the Analyses tab and a toast at the bottom — two of
them visible at once from the editor. The footer, which read "Desktop prêt"
forever, is now the one place: what is happening, for how long, and the
button to stop it. The notices that remain are decisions and offers, and they
sit in the corner rather than across the middle.

One home. "Organisation" was the home page with a flag that changed two
sentences. The header no longer repeats the page title the heading already
carries, and the profile subtitle names the organisation instead of a
constant.
…talled

Four things the interface will ask for (N2, C3, D1, P2):

Local runs are history. Each run on this machine is recorded per folder in
session.json — when, what, how many files, its verdict and its report; the
tool's raw output is not, being large and not what the reader comes back for.
Twenty per folder, newest first.

Every file of a folder as a listing, for finding one by name; the walk skips
what an analysis never reads and stops at twenty thousand.

Which repositories are already cloned, so a card can say "open" rather than
"clone" for a copy that is already here.

Which of ctrace's tools this machine actually has. A run reporting no
findings because three of its tools are missing reads as a clean file; naming
the absent tools before the first run is the difference.
… target

After the audit (N2–N5, S5):

Local runs appear in the history under "Sur cette machine", with the same
row the cloud jobs use and a local badge. Until now only the last one
existed, and only until the next.

A finding is one button. The place to jump to leads the row, in monospace,
because the reader reads the message and then goes to the code; the rule and
tool follow. Before, the location was a muted text button at the bottom.

A local result says "Terminée", "Terminée avec avertissements" or "Échouée"
rather than "Code de sortie 0".

The quote is decided in one place. The Analyses panel shows the price as
information and points to the notice; it no longer carries a second "Lancer"
beside the one in the corner. Its finished-run line stops claiming the
results are open when they are offered.
The editor, after the audit (C1–C3):

Ctrl+P opens a file by name. The tree is fine for seventeen files and useless
for seventeen hundred; the listing comes from the folder walk the local
analysis already does. A name that starts with what was typed beats one that
contains it, which beats a match only in the path.

Analyser was disabled while anything was unsaved, with the reason in a
tooltip nobody hovers. It is enabled; when there are unsaved changes the menu
says "Enregistrer et analyser" and does exactly that, because analysing reads
the disk and the reader wants the file they are looking at analysed, not the
one they last saved.

The empty state under an open folder no longer has a tagline of its own to
compete with the one under no folder.
Dépôts and Paramètres, after the audit (D1–D3, P1, P2, S5):

"Ouvrir dans l'IDE" downloaded the repository without saying so. The card
says "Cloner et ouvrir" until a copy exists here, then "Ouvrir". A disabled
"Analyser" on a repository whose analysis is switched off said no without
saying where yes was; it is now "Activer sur le web". Six cards per page,
which fills three rows of the grid.

The eight-line paragraph under the analyser settings is gone; each row
carries its own sentence. Under the chosen ctrace, the tools it calls are
probed and listed — ✓ cppcheck, ✗ flawfinder — with the consequence spelled
out, because a run that finds nothing for want of three tools looks exactly
like a clean file. The sentence claiming the choice lasts one session was
false since the session started persisting, and the footer no longer names
the stack.
…sages leave

After the audit (A2, A3, T1, S4):

The home opened with a hero, a 52px shield and three action cards before the
first number. Signed in with a folder open, the CTU meter and recent
analyses come first and "Nouvelle analyse" sits where the shield was; the
action cards appear only when nothing is open. Membership reads
"Propriétaire · Actif" rather than the platform's enum values; members are
still listed by id because the platform sends nothing else.

The interface was French until something failed, and then the desktop's
Rust side answered in English, verbatim, in the toast. Those answers are
stable enough to serve as keys: errors.ts is the one place they become
French, and whatever a tool itself printed stays after the translated line
as a detail. Unknown messages pass through rather than being hidden.

A plain message now leaves after eight seconds unless the pointer is on it,
and is a status rather than an alert: an interruption that reads "this commit
was already analysed" is not an emergency. Decisions and offers stay.
Cancelling only raised a flag that the packing, upload and execution loops
poll. Parked on its quote, a run has no loop: the flag was raised, nothing
read it, the notice stayed and the job waited on the platform until its
deadline. Pressing Refuser did nothing at all.

In the Quoted phase the refusal now cancels the job itself and reports the
run as cancelled with nothing spent — which is true, since only the
confirmation spends. The cancel carries the organisation so the platform
knows which job.
…asks

From the sidebar nothing on screen names the open folder, so the button
acted on one the reader may not have had in mind. It now asks — "Analyser
Minishell-main ?" — and offers the other thing "new" can mean: opening
another folder. The home card and the Analyses button, which sit next to the
folder they act on, name it in their label instead of asking.

A confirm can now offer a second way besides the action and Annuler.
What a report is made of, on this machine: the build files at the folder's
root by name, the tool log kept to its head and tail past 512 KiB, the tools
ctrace itself said did not complete, and the first line that reads like the
reason. The send goes through the signed-in session; the platform's
refusals — a limit, a size — come back as the sentences the dialog shows.

A run remembers that it was reported, so the button says so after a restart.
A local result whose tools did not complete offers "Envoyer un rapport". The
dialog says what will be sent, with the real figures; asks "Quelles
librairies utilisez-vous dans votre projet ?"; lists the build files found
at the folder's root, ticked, with a way to add one; and states that the
data stays private and is read only to improve the tools. A refusal from the
platform is shown in place, with the time to wait. Signed-out, the same place
offers to sign in: a report needs a person behind it.
Pushing a tag builds the installers on the three desktops — NSIS, a universal
DMG, deb and AppImage — and publishes them as a release whose notes are
docs/releases/<tag>.md, refused if the notes are missing. A version with a
pre-release suffix is published as a pre-release; a plain one becomes the
latest. Checks still run on every push; the tag-only bundling steps that
lived inside the check job move to a job of their own that waits for it.

Notes for v6.0.0-beta.1, the first Tauri release, are included.
Two files were committed with CRLF by a Windows checkout with autocrlf on,
and Prettier accepted them on one runner and refused them on another. The
attributes make the repository's line endings independent of the committer.
--check only names files; on a runner that is all one gets. Writing the
formatting and printing the diff, with line endings made visible, says why.
…orting

The Linux release job for v6.0.0-beta.1 bundled the deb and then aborted with
"couldn't find a square icon to use as AppImage icon". tauri-bundler picks the
AppImage icon with filter(width == height).max_by_key(width).expect(..), and
the only PNG in bundle.icon was assets/ctrace.png at 524x519 — a pixel shy of
square, enough for the filter to come back empty. Windows and macOS published,
so the release carries no Linux artifacts.

bundle.icon now lists square PNGs at 32, 128, 256 and 512, cut from the same
artwork: the transparent border trimmed and the logo centred on a square
canvas, so the proportions are the original's rather than a stretch. The .ico
and .icns entries are untouched, since Windows and macOS built from them.

The non-square PNG also sent the deb's icon to hicolor/524x519, a directory no
icon theme looks in, so the desktop entry had no icon either.

Bundling only runs in the release job, so nothing caught this until a tag was
pushed. check-bundle-icons.cjs asserts every listed icon exists and at least
one PNG is square, and runs beside the version check in both jobs.
beta.1's release job built the Windows installer and the macOS disk image and
then aborted on Linux, so the release page offered two downloads where its
notes described four. Nothing about the application changes here; this exists
to carry the deb and the AppImage that the icon fix makes buildable.
beta.2's notes pointed at the beta.1 release for what the 6.0 line
introduces, but beta.1 was withdrawn before it was ever public, so the link
would have led nowhere. The one release people will see says everything
itself, with a line on why it is numbered .2.
Ten Electron-era commits — the flawfinder crash workaround, the integrated
terminal shortcuts and the missing-file check — landed on master after this
branch began. The branch never changed those files; the four conflicts were
the branch's line-ending normalisation against master's edits, and are
resolved by taking master's content as it is, in LF.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cc12467d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/analysis.rs
Comment on lines +610 to +611
if let Some(results) = run["results"].as_array() {
findings.extend(results.iter().cloned());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve SARIF structure when merging folder reports

When a per-file report is SARIF, this appends raw SARIF result objects and later wraps them in a top-level findings array. parseFindings treats entries in that array as the custom format requiring a string message and location.path, whereas SARIF results contain message.text and locations; consequently every diagnostic from a local folder analysis is filtered out and the UI reports no problems. Preserve merged runs or normalize each SARIF result into the custom finding format.

Useful? React with 👍 / 👎.

Comment on lines +62 to +66
impl Drop for Running<'_> {
fn drop(&mut self) {
self.0.running.store(false, Ordering::Release);
self.0.cancel.store(false, Ordering::Release);
*self.0.archive.lock().unwrap() = None;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the cloud-run reservation through quote resolution

The Running guard is owned only by cloud_run_start, so it drops and clears running as soon as that command returns a quote, before the quote is approved or refused. Because the frontend does not classify quoted as running, another start can then pass begin() and clear the stored job at line 87, overwriting the phase and stranding the first quote or corrupting state while its confirmation is in progress. Keep the reservation active until the quoted job reaches a terminal decision.

Useful? React with 👍 / 👎.

Comment thread desktop/App.tsx
Comment on lines +781 to +784
<button
className="primary"
disabled={cloudRun.busy}
onClick={() => void cloudRun.approve(cloud.org)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind quote actions to the run's organisation

If the user changes the enabled organisation selector after a quote appears, approval is sent with the newly selected cloud.org rather than the organisation that created the upload and job. The native client scopes confirmation with that value in X-Org, so the request can fail in the wrong tenant; cloud_run_confirm then replaces the quote with failed, leaving no UI action for the original awaiting-confirmation job. Store the run's organisation with its state or prevent organisation changes until the quote is resolved.

Useful? React with 👍 / 👎.

Comment on lines +184 to +185
for _ in 0..120 {
let status = session

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor cancellation while verifying an upload

When cancellation is requested during the verifying phase, cloud_run_cancel sets the atomic flag and returns successfully, but this polling loop never checks it. The application therefore continues making verification requests for as long as 120 seconds and keeps displaying verification before the later check in run_until_quote finally observes cancellation. Check the run state inside this loop so the user's cancellation takes effect promptly.

Useful? React with 👍 / 👎.

Comment thread desktop/App.tsx
Comment on lines +643 to +644
stdout: "",
stderr: "",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not submit empty logs for historical failures

When a user opens a failed run from local history, this constructs a LocalResult with empty stdout and stderr, yet the Analyses page still offers the support-report action based on the stored warnings or exit code. ReportDialog consequently submits an empty log and then marks the run as reported, preventing a useful retry even though the dialog promises the complete ctrace log. Persist the output needed for reporting or disable reporting for history entries that no longer have it.

Useful? React with 👍 / 👎.

/** What the platform accepts: log plus files. */
const CAP = 2 * 1024 * 1024;
const LOG_LIMIT = 512 * 1024;
const DESKTOP_VERSION = "6.0.0-beta.1";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report the actual desktop build version

The released package, Cargo crate, and Tauri configuration are version 6.0.0-beta.2, but every support report sends this nonempty 6.0.0-beta.1 literal. Since support_send only substitutes CARGO_PKG_VERSION when the field is empty, reports from beta.2 are permanently attributed to beta.1, which can misdirect failure triage. Derive this value from the build/package version rather than maintaining another literal.

Useful? React with 👍 / 👎.

@cedricroulof
cedricroulof force-pushed the feat/tauri-react-desktop branch from a90c611 to 3475bde Compare September 18, 2026 12:15
@cedricroulof
cedricroulof merged commit a800e93 into master Sep 18, 2026
8 checks passed
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.

2 participants