Rubra Candela
Changelog
0.2.5
10 September 2026.
The graph is readable where it was densest
File names collided into a smear in exactly the places you most want to read them — the crowded middle of a project. Whether a name appeared depended on how big its node was, which is a guess at how crowded things are, and in a cluster that guess is always wrong.
Names are now placed by checking whether they would actually overlap. What you have selected or are pointing at is always named; the rest fill the space that is left, largest first. Fewer names, all of them legible, and it adjusts itself as you zoom.
They also used to shrink with the graph, so zooming out to see the whole project rendered them at around seven pixels. A name is now the same size to read at every zoom.
Long lines can wrap
The code panel is a few hundred pixels wide and clipped nearly every real line, so reading a file meant scrolling sideways for each one. There is a wrap button next to the file now. It is off by default, because indentation means something in most languages and wrapping breaks the column it lives in, and it remembers your choice.
Colour says how much to worry
Circular dependencies and unused files both turned red the moment either was non-zero, so a project with both showed two identical red numbers and nothing distinguished them. A dependency cycle is a fault and stays red. An unused file is usually an entry point and is now amber — worth a glance, not an alarm.
The keyboard is visible again
Most of the app could be tabbed through with nothing on screen indicating where you were: a focus outline existed, and only a quarter of the buttons used it. Every control shows one now.
0.2.0
9 September 2026.
A route that should only say "I am here" was saying more
The app runs a small server on your own machine, and one endpoint exists so the window knows the server has started. It cannot require a sign-in — it is what runs before you sign in — and it was answering with the full path of the open project. On Windows that path contains your account name.
Nothing could reach it from a website, and nothing on your machine is known to have looked. But a route whose entire job is to answer "yes, I am listening" should say that and stop, and now it does.
Two things that only went wrong slowly
Repeated failed sign-ins are throttled. The record of them was only cleared by trying the same address again, so failures spread across many addresses grew that record for as long as the app stayed open. It is now bounded, and old entries are dropped rather than waiting to be asked about.
Expired sign-in sessions were cleared when the app started. That is enough if you close it, and not enough if you leave it running for weeks. They are now also cleared whenever you sign in.
Checked and found sound
Two things worth stating plainly, because they were tested rather than assumed:
- A folder containing
id_rsa,.env,credentials.json, a private key,.npmrcand.netrcwas opened as a project. Exactly one file was read — the ordinary source file sitting beside them — and every credential file is refused, including to the app's own interface. - The five language parsers added in 0.1.9 were given deliberately hostile input designed to make pattern matching hang. The worst case finished in a millisecond.
0.1.9
8 September 2026.
Five more languages actually have a graph
Rubra Candela recognised thirteen languages and could read the imports of three. For the rest it drew your project as disconnected dots: no connections, nothing in the inspector, no dependency list — and, because they are all built from connections, no circular dependencies, no unused files, no impact analysis and no hotspots.
Go, Java, Kotlin, Rust and C# now work properly. A five-file Go and Rust project that produced nothing at all now produces the graph it should have all along.
Each language decides for itself what an import points at, because they do
not agree. A Go import names a package, which is a directory, so it connects
to every file in it — that is how Go compiles, and picking one file would
look neater while being a guess. Java and Kotlin source roots are worked out
by subtracting a file's declared package from where it actually sits, so no
directory layout is assumed. C# follows the namespace a file declares rather
than the folder it sits in, because the two are only conventionally related
and a project that diverges would otherwise get connections that are simply
wrong. Rust separates mod from use, because use a::b::C genuinely does
not say where the module ends and the item begins; where it cannot be known,
nothing is drawn.
That last rule is the one that matters everywhere: a missing connection is a gap, and an invented one is a lie you would act on.
Fixes
go.modwas never read, so a Go project's own packages were listed as third-party dependencies.- Java wildcard imports —
import com.example.util.*— resolved to nothing. - Unused-file and hotspot analysis kept a private list of the languages it understood, and would have skipped the new ones without saying so.
0.1.8
5 September 2026.
Naming the wrong folder looked like the app breaking
Pointing Rubra Candela at a folder that is not there reported a server error, with the raw filesystem message attached. Choosing a folder is the first thing you do and mistyping one is the first thing that goes wrong, so it should read as what it is. A folder that does not exist now says so, and a file given where a folder was expected says that instead.
Requests nobody made were being answered
Sending a number where a folder path belongs quietly resolved to a folder named after that number, next to whatever project was open. Sending a list of one engine tier selected that tier. Neither is a request anyone would make on purpose, and both are now refused.
Long credentials were slipping past the scanner
The scanner gave up on any value over two hundred characters, so a long token
sitting next to a name saying apiKey went unreported — exactly what it is
supposed to catch. It now reads far longer values.
Nothing was loosened to do it. Every case the scanner is meant to ignore still is: commit hashes, identifiers, file paths, placeholders, ordinary prose, and an embedded image assigned to a name containing the word "secret".
0.1.7
28 August 2026.
Refusals said the wrong thing
Asking for a file the app protects — .env, key material, anything outside
the project — was correctly refused, and then described as a server error.
The refusal was right; the explanation was not, and "something went wrong"
reads as a fault to fix rather than a rule being enforced.
Two endpoints already answered properly and the rest did not, for exactly the same refusal. They all answer properly now.
One request was not checked at all
Asking for the commit history of a protected file returned an answer instead of a refusal. Nothing sensitive came back — but the history of a file the app promises never to open is still something about that file, and it should never have been served. Every endpoint that takes a file now applies the same check, and there is a test that asks each of them, so this cannot quietly return.
Rewrites that could never have been saved
A file too long to load in full could still be sent to the engine for a rewrite. Saving the result was refused afterwards — correctly, since only part of the file was ever read — but by then the engine had spent minutes writing it. Files like this are now declined immediately, with the reason.
Smaller
- Update checks go straight to the address that serves them rather than following a redirect on every attempt.
0.1.6
25 August 2026.
Two of the five engine tiers could never be selected
Clicking the smallest or the largest tier answered "Unknown tier" and downloaded nothing. Everything else about them was real — they were listed, sized, described, and reported as available — but the request to switch was refused before a download could start.
0.1.4 introduced five tiers where there had been three. The part of the app
that accepts a tier change was written when there were three, listed those
three by name, and was never updated. So tiny and max have been
unreachable in every build that has advertised them, and the failure looked
like a download that would not start rather than a request that was rejected.
Both work now, and the check is derived from the tier list itself rather than written out again, so the two cannot fall out of step a second time.
If you have been trying to download the largest tier and watching nothing happen: it was not your machine, your network or your disk.
Download progress no longer sits at nothing
A model download reported no progress until its first chunk arrived. On a twenty-gigabyte model that is a long silence, and indistinguishable from being stuck.
0.1.5
22 August 2026.
It finds credentials you have committed
Keys pasted into a source file are the leak that matters, because they get committed, pushed, and stay in history after you delete the line. Rubra Candela now reports them: provider tokens are recognised by their documented shapes, and anything else has to be both named like a secret and random enough to be a generated one.
A finding never contains the credential. It is the first four characters, a length, and a file and line — enough to go and fix it, and not enough to leak again through anything you paste. Files whose whole purpose is holding secrets were already refused by the scanner in 0.1.4; this is the other half.
The work went into what it does not report. Lockfiles and generated bundles
are skipped, values shaped like module paths are rejected, and placeholders like
your-api-key-here are left alone. One wrong flag on a test fixture teaches you
to dismiss the whole feature, and a dismissed warning protects nobody.
A path in an answer opens the file
When an answer cites a file, the path is now a link, and clicking it opens that file in VS Code at the line if the engine gave one.
Paths the engine invented stay exactly as they were in 0.1.4 — marked in amber, and deliberately not clickable. An answer that cannot be trusted about whether a file exists should not also offer to open it.
The engine can propose a change
You can describe a change to the file you are looking at and have the engine write it. What comes back is a diff to read, not an edit that has happened.
Nothing about this path writes to your disk. Accepting a proposal puts it in the editor, still unsaved, and the file is written by the save you press yourself — through the same checks as anything you typed by hand, including the guard that refuses to overwrite a file that changed underneath you. The engine never gains a capability you do not already have.
Because a local model will confidently delete something it was not asked to, every proposal is checked for what it removed. Asked to simplify one file during testing, the engine dropped two exported types and explained that they "were not being used" — one of them was the return type of the function it had been told to keep. A diff showing 71 deleted lines reads much like 71 deliberate ones, so the proposal now says which exports disappeared. It also catches the way smaller tiers fail, which is returning half a file.
Better at choosing what to read
The engine can only be as right as the files it was given, and it was being given the wrong ones.
Query words were matched anywhere in a path, so a question about which files get
read ranked ChatThread.tsx first — "thread" contains "read". Words like
"code" and "path" were treated as though you were naming an identifier, so
asking about "the authentication code" put the code viewer above the
authentication module, and asking about "the file writing path" returned the
build scripts. And a question about "investigations" never found investigate.ts
at all, because neither word is a prefix of the other.
All three are fixed, and answers now also read the files around the ones that matched. A function is rarely wrong on its own; it is wrong about what its callers hand it, and the import graph knew that and was going unused.
Fixes
- The largest engine tier was reading the same amount of code as a mid-sized one. Every tier now has its own budget, and the largest reads seven times what it did before — which was the only reason to run it.
0.1.4
14 August 2026.
Edit and commit without leaving
Files opened in the Code tab can now be changed. Editing is deliberate — you start it, the tab marks itself unsaved, and nothing reaches disk until you save. The diff of your changes is there to read, and you can commit straight from the app.
Saving is careful about a case that is easy to get wrong. If the file changed on disk after you opened it — another editor, a branch switch, a teammate — the save is refused rather than silently overwriting the newer version, and you are shown the choice between reloading and overwriting anyway. Writes replace the file in one step, so an interrupted save cannot leave you with half a file.
Five engine tiers instead of three
There is now a smaller tier for modest machines and a considerably larger one for people with the graphics memory to hold it, either side of the three that existed.
Any tier can be downloaded and used, including one your hardware was measured as too small for. That measurement is a guess about available memory; if you would rather wait longer for a better answer, that is your decision. Tiers expected to be slow on your machine are labelled, not blocked.
It tells you what a question cost
While the engine is thinking, a bar shows elapsed time. When the answer lands it reports how many tokens were read and written, and the speed it managed. A local model is slow enough that the difference between working and stuck matters, and the numbers make the tier choice concrete rather than abstract.
Answers no longer point at files that do not exist
The engine occasionally cited a file it had invented — confident, plausible, and not there. Every path it mentions is now checked against your project, and anything absent is marked in amber rather than presented as fact. The rest of the answer is left alone, because the point being made is usually still sound.
Fixes
- Files holding credentials —
.env, private keys,.npmrcand similar — are no longer read by the scanner at all. They never enter the project graph, and the app will not open them. - Imports that could not be resolved were being recorded as external packages,
so a broken
@/…path appeared in your dependency list as though it were something you depended on. Python packages now group under their real name rather than one entry per import. - Viewing changes for a file failed silently in 0.1.3. Diffs work.
0.1.3
11 August 2026.
Git intelligence never worked
Every version until this one reported that your project was not a git repository, even when it plainly was. No branch, no commit history, no sense of which files change most often — the whole feature was missing, and it failed quietly enough that nothing said so.
The cause was a piece of hardening added to protect you. A git repository can carry settings that make git run programs on your machine when a tool reads it, so Rubra Candela overrides those settings before touching any repository. The library it uses to talk to git had since begun refusing to apply exactly those overrides unless asked explicitly. Every git command failed, the failure was swallowed, and the feature disappeared.
Git now works, and the protection is still in place — verified together rather than one at the expense of the other.
The app misreported its own version
Installed copies identified themselves as 0.1.0 no matter which version they were. Nothing you could see, but it meant update checks and install records were working from the wrong number.
Choose your own engine tier
The tier list in Settings has always shown which tiers exist, which are downloaded and which one your hardware was matched to. Now you can act on it — click any tier to download and switch to it.
Including one your machine was measured as too small for. That measurement is a guess based on available graphics memory, and it is only ever a guess; if you would rather wait longer for a better answer, that is your call to make and the app will not argue. Tiers it expects to be slow are labelled, not blocked.
Smaller
- Errors from the local API return JSON instead of an HTML page, so anything built against it gets a usable message.
0.1.2
10 August 2026.
The updater was never switched on
Rubra Candela checks for updates when it starts and whenever you open Settings, and it marks the Settings icon when there is one. All of that worked. What was missing was the address — the setting telling it where the version manifest lives had never been filled in, so every check returned "no update channel configured" and quietly did nothing.
It now points at rubracandela.com/latest.json, which is generated from the
same file that produces the download button and the published checksum, so it
cannot fall out of step with the installer being served.
If you are reading this in 0.1.0 or 0.1.1, that copy will never notify you. The address is compiled into the application, and those builds do not carry it. This is the last update you will have to find by hand.
The update button did nothing
When an update was found, the button offering to install it was permanently disabled — a packaged application cannot replace itself while it is running, and enabling that path would have meant executing whatever a downloaded manifest told it to. The only control that worked was a small link labelled "notes", which confusingly pointed at the installer.
The download is now the button, it says which version it will fetch, and it explains that running it over your existing copy keeps your account, settings and cached analysis.
0.1.1
10 August 2026.
Two fixes, both reported by people trying to use 0.1.0 for the first time.
Signing up asked for a code and gave you nowhere to type it
A verification code was emailed the moment you created an account, but the app went straight to the graph and never mentioned it. The only place to enter the code was a box inside the Settings panel — which nobody opens thirty seconds after installing something.
Signing up now stops on a verification step: the code goes in there, with the address it was sent to shown above it, and buttons to resend or to skip. It is still not a blocker. A mail outage, a spam filter or being offline must never lock you out of software that runs entirely on your own machine, so I'll do this later takes you straight into the app and the box stays in Settings for whenever you want it.
If sending fails, the screen now says so instead of leaving you waiting for an email that is not coming.
Installing Ollama took more steps than it should have
When the local engine was missing, the app told you to install Ollama from ollama.com and left you to work out the rest — on a page offering three platforms and a command line.
There is now a Download Ollama button that starts the Windows installer directly. Run it, press Retry, and the engine comes up.
0.1.0
First public release — 8 August 2026.
The first build anyone outside the project has been able to run. It does the whole job end to end: point it at a folder, see the shape of the code, ask it questions, get answers that cite the files they came from.
Reading a project
- Resolves imports the way a bundler does, including
@/path aliases, npm workspaces and Python packages, rather than matching text. - Parses TypeScript, JavaScript, JSX, TSX, Python, JSON, Vue and Svelte for imports. Files in other languages still appear on the graph and can be read and explained; they are simply not traced for dependencies.
- Watches the folder while it is open, so editing a file in your own editor updates the graph without a rescan.
- Very large repositories are truncated at a scan limit rather than freezing, and the app says so when it happens.
The graph
- Force-directed layout of every file and every import, with zoom, pan and search.
- Node size is lines of code; colour is complexity.
- Selecting a file highlights everything it touches, in both directions.
Explanations and investigation
- Plain-language explanation of any file: what it is for, what it is responsible for, how it connects, and what is risky about it.
- Open-ended investigation — loopholes, broken ends, unfinished work — answered by reading the actual code, with the files it consulted listed as evidence.
- Built-in code viewer with syntax highlighting, so a file can be read without leaving the app.
- Everything runs through a language model on your own machine, via Ollama. Questions and answers are cached locally and never transmitted.
Analysis
- Circular dependency detection.
- Dead code: files nothing imports.
- Complexity and size hotspots.
- Git history — which files churn, who touched them last, where recent commits concentrated.
- Full-text search across the project, with results linked to the graph.
Accounts and privacy
- Analysis needs no account. Signing up sends your name, email address and country once, and nothing after that.
- Passwords are hashed on your machine with scrypt and never leave it.
- The privacy policy enumerates every network connection the application makes. There are four, and three of them are optional.
Known limitations
- Windows only. The application is built to run on macOS and Linux, but signed and notarised builds are not ready.
- Unsigned. Windows SmartScreen will report an unknown publisher. The installer is safe to run, and you can verify it against the SHA-256 published on the download page, but the warning is real and will appear until there is a code-signing certificate.
- Ollama is a separate install. Bundling it would triple the download and conflict with any copy you already have.
- No plugin API, no team features, no remote repositories. Local folders only.
Rendered at build time from content/CHANGELOG.md —
checked against the installer this site is serving.