Repository navigation
CoreTrace Desktop 6.0: the Tauri application - #79
Conversation
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.
There was a problem hiding this comment.
💡 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".
| if let Some(results) = run["results"].as_array() { | ||
| findings.extend(results.iter().cloned()); |
There was a problem hiding this comment.
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 👍 / 👎.
| 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; |
There was a problem hiding this comment.
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 👍 / 👎.
| <button | ||
| className="primary" | ||
| disabled={cloudRun.busy} | ||
| onClick={() => void cloudRun.approve(cloud.org)} |
There was a problem hiding this comment.
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 👍 / 👎.
| for _ in 0..120 { | ||
| let status = session |
There was a problem hiding this comment.
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 👍 / 👎.
| stdout: "", | ||
| stderr: "", |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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 👍 / 👎.
a90c611 to
3475bde
Compare
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 onmaster, normalised to LF.Release notes:
docs/releases/v6.0.0-beta.2.md.