What's New
What changed, why it changed, and what it means for your clinical workflow. Newest releases first.
## 0.5.34—August 2026
### The Release That Would Not Open
Start with the worst of it: 0.5.33 does not start. The backend dies on `import cw_eeg.server` with `ModuleNotFoundError: No module named 'cw_phi'`, the splash closes, a dialog says the analysis engine failed to start, and the app quits. The PHI redaction package is a local path dependency rather than something on PyPI, so it is present only where a build installs it by name. Continuous integration has done that for months. The release workflow never has, and PyInstaller cannot bundle a package the build environment does not have, so no release build has ever contained it. This was already broken; 0.5.33 was simply the first release to import the redaction stack at server-module scope, which turned a silent feature outage into a dead app. Before that the miss surfaced one feature at a time and misreported itself every time—uploading a clinical document returned advice to install a PDF library that was in fact already bundled, when the real answer was that document redaction had never run in a packaged build at all. The installer shipped Presidio, spaCy and a 560 MB language model it could not reach.
The import was not softened, and that is deliberate. Being unable to de-identify is not a degraded mode, so the import stays hard and the bundle gets fixed. The release workflow installs the package, the packaging spec names the first-party packages explicitly and treats a collection miss as fatal rather than a warning, and—the part that generalizes—the build now boots the packed backend on a scratch port and waits for it to answer before anything is published. Listing packages only defends against the misses somebody thought of; running the artifact catches the next one too. The health endpoint the app already polls at launch now also reports whether redaction, Presidio and spaCy actually shipped, proved by locating each module rather than importing it, since importing spaCy would spend seconds of the thirty-second startup budget on every launch.
Then there was the silence around the failure. Backend output goes into a 2,000-line in-memory buffer forwarded to renderer windows, and on a failed start there are no renderer windows—the buffer died with the process, so the one report we most needed was the one we never wrote. The tail of it now lands in `desktop.log` before the app quits and the dialog says where, and a new `scripts/diagnose-engine-start.sh` recovers the reason on a machine that is already failing: it names the build, tails the log, checks whether something else holds the port, checks quarantine and signing state on the nested backend, and then runs the bundled engine by hand so its own traceback is visible. It distinguishes "died on import" from "booted but took longer than the app allows," which need opposite fixes. A failed redaction had the same shape of problem—it wrote nothing, and a session whose redaction failed looked exactly like a session with no case history attached. Fail-closed is right; invisible is how this hid for months. A failure now records itself, a later success clears it, and the supplementary-context block tells the reader the document is being withheld rather than quietly proceeding without it.
### Where the Signal Actually Came From
The source localization was wrong, and it had been wrong since May. `build_bem` handed its three boundary surfaces to MNE innermost-first, and on that particular call path MNE does not reorder them—so the model placed outside air inside the *brain* and applied the isolated-skull correction to the *scalp*. No error, no warning; the nesting checks live only on the other path. The resulting lead field was roughly thirty times too weak and badly distorted.
The way to see it is to fit a dipole to something whose location is independently known. On a frontal-midline theta component that EEGLAB's own DIPFIT localizes to anterior cingulate at 1.0% residual variance, the broken assembly put the best-fitting source in superior frontal gyrus at 4.68% residual variance; assembled correctly it lands in anterior cingulate at 0.98%. Colin27 turns out to be a good head model—the corrected result beats both fsaverage and a fitted three-shell sphere. The assembly was the defect. Every artifact built through that function carried it too, so the bundled forward operators and the transformation matrices behind the IC source rail were regenerated: over forty test sources the transformation matrices now recover a known location to a median of 5.0 mm where they were 19.9 mm off, with a worst case of 16 mm rather than 56.
Parcel naming was a second, independent error in the same area. Grid voxels were labelled by nearest region *centroid*—a Voronoi partition over sixty-eight points standing in for sixty-eight irregular, wildly unequal regions—which agreed with the true parcellation 53% of the time and was biased rather than merely noisy. Its single most common mistake was calling superior frontal *caudal anterior cingulate*, which is to say it manufactured the finding clinicians are most primed to look for. Voxels are labelled by nearest cortical vertex now, and the lookup reports its confidence from the distance to the ribbon rather than asserting one.
None of that would have reached an existing session on its own. The artifact bus is content-addressed on producer identity and parameters, and the bundled head model is not part of that key, so a session warmed before today would have gone on serving its stale, wrong source results forever, silently, on every install. Four producer versions were bumped to force the recompute. The magnitude is worth being plain about: on one real resting session with the forward as the only variable, all twelve key source findings change. Which region carries the strongest alpha connectivity, which network is most internally coherent, which network pair couples most strongly—all of them move, and hemispheric lateralization reverses sign on several networks. **Any source-space report generated between May 2026 and this release should be regenerated.** The May equivalence audit that concluded Colin27's disagreement with fsaverage was inherent rather than a bug was measuring the broken forward, and its central check could not have caught this: seeding a source through a model and localizing it with the same model is circular, and a model with its shells in the wrong order is physically wrong and perfectly self-consistent. That report is corrected on the record, and head-model changes are accepted on a dipole fit from here on.
### Analysis on the Part of the Recording You Meant
Two ways a recording can quietly stop being the recording you think you are analyzing, both now under the clinician's hand.
The first is drowsiness. A normative comparison computed over ten minutes in which the subject drifted into stage A2 for four of them is a comparison against something other than resting alertness, and nothing said so. The vigilance producer now emits its own alert and excluded windows once, in the module that owns the staging, so the frontend and the transient gate read one answer instead of each deriving their own—which matters because stage 0 means opposite things in the two resting conditions, and a hardcoded alert set was wrong for eyes-closed, where stage 0 is alpha dropout rather than alertness. A Vigilance restriction control in the Clean tab switches analysis to alert epochs only. It is off by default, refuses below thirty seconds of alert data and warns below sixty, writes the excluded stretches as one op-stack edit and one journal entry attributed to the clinician rather than the system, and draws those stretches on the trace in the drowsy stage color so they read as drowsiness rather than as artifact. The Vigilance phase states what is on and points at the single place it can be changed. Every surface that renders a normative z-score—the vigilance card, the client report's Chain of Reasoning, the IQCB regulatory report's quantitative section—now discloses the restriction and how much of the recording it kept, because a normative comparison on a selected subset that does not say so is misleading. The data-removed breakdown counts drowsiness apart from artifact, since a clinician who has removed 6% for artifact and 38% for drowsiness is in a very different position from one who has removed 44%.
The second is movement in the ICA fit. A few seconds of a subject shifting in the chair dominates a variance-driven decomposition, and the components come back modelling one burst each instead of separating physiology. Clinicians could already paint those spans, and the spans already left the spectra and connectivity math, but they never reached the ICA and there was no way to say "now, please." There is now: a button on the surface where the spans already live, which snapshots the current spans and re-fits against that snapshot rather than against the live list, so painting a span changes nothing until the next press. That gap is what the surfaces render as "4 marked, 3 in the fit." Pressing clears the component rejections, because the new decomposition's IC 3 is not the old one's IC 3—stated before the click, journalled with the count, and undoable with the previous fit's cache key intact. A fit that would leave under sixty seconds, or drop more than half the record, raises rather than quietly fitting the whole thing. Relatedly, ASR and the fit exclusion were both missing from the count of unsigned changes, which meant either could be changed after sign-off while the app still reported the cleaning as signed and every pane kept rendering the previous stack. Both count now.
### Reading the Trace Before the Numbers
The QEEG program's first phase has always been a cold read: look at the raw trace, form an impression, before any spectrum or head map is on screen. What it lacked was an answer key. The Surface Read Drill supplies one. You draw a box around what you see, the app fits your selection and tells you what is actually in it, and the finding rows fill in as you go. Nothing is ever scored as wrong—there are five outcomes and none of them is that—because the peak fit has real false negatives, and an exercise that answers "nothing there" to a genuine finding teaches exactly the wrong reflex. A row you cannot crack has a four-rung ladder behind it: band, then region, then a call to compare that scrolls the trace, then the rule's own sentence. No model is called at any rung; every hint is generated from the values the rule already carries. The fourth rung is always available, counts as recognized, is marked as hinted, and costs score rather than progress. Practicing again is the normal case rather than an exception, so it summarizes the attempt and blanks the rows instead of deleting the file—"revealed at level 4, then found unaided next time" is better evidence of learning than either half alone, and the panel says how many attempts came before.
Building it drove a good deal of work in the finding catalog itself, because a drill asks a clinician to find everything that is there while the catalog was built to state one finding in one sentence. Four rules that narrowed to a single strongest site now report every site they qualified—measured across fifty-six recordings, the discarded sites were not rare, and on one real record a frontal theta rule qualified six channels and reported one, throwing away the F7 slowing that seven other rules independently name. Two gates were sitting below the median of the very quantity they gate, describing the ordinary spread rather than picking anything out, and have been retuned against measured corpus distributions. A new rule finds the alpha that *stayed put* when the rest of the head blocked, measured relative to the subject's own occipital retention because absolute retention across the corpus runs from 27% to 122% and no fixed threshold separates anything. Another check asks whether a site owns its rhythm or is merely receiving posterior alpha arriving by volume conduction—across forty-five recordings, zero met the mu rhythm rule's own stated criterion, and the check does not only remove noise, it promotes the two genuine mu and frontal rhythms that were buried among the derivative ones. And a fragment filter stops the drill asking anyone to find a piece of a rhythm that the peak fit happened to describe with two overlapping curves; frequency distance cannot solve that, but walking the spectrum between the two peaks and asking whether it ever comes back down can.
Underneath all of it is a new descriptive artifact that clusters per-channel spectral peaks into rhythm families, with montage adjacency scaled to electrode density rather than a fixed distance, a complete-linkage join rule so a family cannot hill-climb transitively past its own tolerance, and amplitude reported in microvolts. That last one immediately fixed a rule that had never fired: low-voltage EEG was gated on log-prominence above the aperiodic fit, which barely tracks voltage at all, and fired on none of fifteen recordings. Gated on measured amplitude it fires on exactly the five low-voltage recordings out of nine and none of the four normal-voltage ones.
The measurement tool the drill leans on got better in its own right. Dragging a span on the trace now reports how *big* the rhythm is and not only how fast, fits the selection rather than guessing at its shoulder, and answers instantly from a local estimate before the server's fit replaces it. One bug found along the way was worth the whole exercise: dragging one channel onto another to compare them drew the overlay upside down relative to the trace beneath it, because the overlay mapped voltage the natural way and everything else in the trace uses the clinical negative-up convention. Two sites correlating at r = +0.88 rendered as a perfect phase reversal. Judging whether two channels are in phase is the entire reason that gesture exists, and it had been inverting the answer every time.
### Panes That Move
Phases used to be fixed grids. They render as resizable split trees now: every boundary drags, and where you leave it is remembered per program and phase on that machine. The inspector and the chat are real collapsible panels in the same group rather than a sliding overlay and a width spacer, they stay mounted when collapsed so a live conversation survives, and the keyboard shortcut, the command palette and the header button all move the same panel a drag would. A "Reset layout" command puts everything back. The Spectral phase went first as the pilot, reproducing its shipped proportions exactly so nothing looked different on the day it became draggable; Vigilance, Substrate, Sources, Network, Longitudinal and the four multi-pane ERP phases followed, and plugin programs get a tree derived from their existing pane packing for free. Dragging re-fits the plots once on release rather than once per frame, so a long drag across a pane holding the trace canvas and six topomap strips does not spend a full re-layout on every frame of it.
### A Finding From an Image Made Somewhere Else
Some of what belongs in a report was never on screen in this app—a figure from another analysis package, a photo of a montage, a screenshot from a referring provider's report. You can now file a finding directly from an image, decoded and downscaled on the way in, marked in the rail and in the rendered report as externally sourced so nobody later mistakes it for something this app measured, and gated behind a step that asks you to confirm the image carries no identifying text. You can also swap a better image into an existing finding without recreating it.
The annotation set gains a blackout, which hides rather than highlights, and the important part is what happens at save: it is burned destructively into both stored images, and the annotation does not persist as an ordinary deletable overlay. That closed a real gap. Three surfaces—the Surface EEG trace in both shells, and an observation promoted from the AI Technician—were compositing the blackout for display and then persisting the un-redacted capture underneath it, so a blackout drawn there never actually covered the pixels in storage. All of them route through the one implementation of that contract now. Report images also got a click-to-enlarge overlay, in both the interactive HTML report a clinician opens and the PDF builder, which are independent document builders that had already drifted once.
### The Numbers on the Legend
A color scale that labels itself with numbers the canvas is not using is worse than no scale. In the diverging head-map modes the map was drawn on a symmetric range centred on zero while the colorbar was labelled from the raw data minimum and maximum, so a genuinely positive value could render as a faint tint under a legend saying it should be saturated—reported, reasonably, as the maps and the table disagreeing. They never disagreed; the legend did. The bar now reports the range the canvas was actually given, which also puts zero in the middle of a difference scale by construction, and this one was fixed in the older shell too, because a mislabelled clinical color scale is not something to leave behind a sunsetting policy. The network topomap strip had the same class of defect from the other direction: each band head is centered on its own midpoint before painting, so the range bars were labelled in centered space while the hover readout reported raw node strength, and one electrode could read 0.298 on a bar whose top tick said 0.140. The bars read in raw units now, with the band's own midpoint as the middle tick, and no pixel changed.
Head spectra gained the hover readout in compare mode, which is precisely where it had been missing, reading both recordings and the change between them; double-clicking a cell solos that channel to fill the pane with proper labelled axes, because reading an alpha peak off a ninety-pixel thumbnail is not realistic.
Reports picked up several corrections. The glossary that reports render from is a build artifact generated from the app's own glossary, and it had not been regenerated when the research-use wording was softened three weeks earlier—so every rendered report had been carrying the old, stronger claims while the app showed the softened text. It is regenerated. Finding descriptions and clinician notes render Markdown now, through one shared renderer with raw HTML escaped, which fixes the specific complaint that deliberate line breaks collapsed and asterisks appeared literally. The clinical-register gate, which is a fail-closed backstop against a diagnostic claim reaching a rendered report, could not previously tell an asserted diagnosis from a reported one, so a clinician's own note describing a diagnosis another provider made blocked delivery with a raw error blob and no way through. Delivery now offers a panel listing each flagged term with a reason field; cleared terms are subtracted from the gate and everything else still blocks, the clearance is keyed to the exact flagged text so introducing a new term re-blocks rather than riding a previous clearance, and the whole thing persists on the draft as a trail. Long report sections also reveal on scroll again—the observer threshold required 12% of a section to be visible, which a section taller than about eight screens can never satisfy.
### Settings That Are Actually Used
Two workspace defects that look small and are not. New sessions resolved their filter settings against a hardcoded module constant instead of the workspace's configured default, so every imported session was processed at 1.0–100 Hz regardless of what Settings said. A 1 Hz high-pass attenuates delta and a 100 Hz low-pass admits muscle into every downstream spectral measure, and both then read as clinical findings. And because a session's folder gets a suffix when its date collides, the folder is the session's identity—but the session list trusted a stored date field instead, so a legacy or hand-copied record could make two sessions report the same date. Everything keyed on date then addresses the wrong one: comparisons, longitudinal views, export, and baseline pinning, which is where it was caught. The date comes from the directory now, so a stale field self-heals on read.
On the autonomic surface, HRV metrics can be compared against the pinned baseline session—the first tabular compare-prior surface, so it reuses the existing baseline plumbing rather than inventing a diff rendering. Percent change is shown for mean heart rate, SDNN and RMSSD and deliberately suppressed for pNN50 and LF/HF, which are already a percentage and a ratio respectively and where a percent change is arithmetically definable and clinically deceptive. Where a mundane explanation exists it is named: breathing rate differing by three breaths per minute or more, recordings differing in length by half again, or valid beats under 95% on either side. These say "interpret with care," never "this change is not real." The HRV producer itself was moved to the front of the warm queue, where it should have been all along—it reads one upstream artifact and does about a second of arithmetic, and being seventh in the review order had parked it behind the normative chain, the ICA fit and both connectivity producers, none of which it consumes. On one measured session that was nearly three minutes of queueing for one second of work, with the clinician watching an empty pane for all of it.
### Dictation, Held at the Door
Voice narration is in, off by default, and switched on per machine under Settings → Experiments. You dictate while you work; a locally bundled Whisper build transcribes it on your machine with a hallucination guard; the transcript is de-identified before any model sees a word of it; and a single distill pass reshapes what you said into draft text with a stenographer's brief—it may reorganize your words and may not add interpretation. What comes back are drafts, not writes. They appear as chips beside the finding dialog and the report writer's section editor, and inserting one copies the text into the editor you already have open so that your own Save is what persists it. Nothing in the voice path writes a finding, a framing or a report section directly. Findings are matched to the moment you were talking about them by deterministic timestamp windowing that accounts for pauses, not by asking a model to guess.
### Chart, On Its Own Cadence
Coherence Chart, separated out last release, now has a release pipeline of its own: its own tag, its own draft, its own download page, both products building through one shared workflow definition rather than a forked copy that would drift within two releases. The shipping script takes an `--app` flag for the same reason. Inside Chart, the plan timeline now shows what was observed while an intervention ran and marks where a course was stopped and why, rather than a bar quietly running out—when something was discontinued is usually the clinically interesting moment. The findings list reads as a list again, with titles that look like titles and notes that keep their paragraph breaks, and a finding keeps its superseded readings under a collapsed "How this reading changed," because a clinician note that gets overwritten takes its own history with it and makes "when did you conclude that, and on what basis?" unanswerable.
The consideration Domain Lens is rebuilt and gated. Clicking a domain reads the whole chart through that domain's curated profile and returns two things: findings the profile treats as relevant that the canvas has not taken up, and concepts the profile calls load-bearing that the case never references. The second is what earns the click, because it is the thing a clinician cannot see by looking at what they already have. What is deliberately not rebuilt is the half that got its predecessor deleted—there is no AI declaring a domain relevant or not, no status vocabulary in the schema or the prompt, and the validator rejects a tool call carrying one. Both outputs are pointers into documents the clinician owns, and a pointer to something that does not exist is rejected rather than trimmed.
### Known Issues
The Surface Read Drill gates nothing yet. It runs on the Cold Read phase and grades what you mark, but advancing the phase is untouched, and gating is deliberately last because it is the only part that can obstruct an existing clinical workflow. Which findings count as required is provisional—currently the rarest one—pending a visibility pass that has not been done. And the answer key is the finding catalog, which says so on the panel: eighty-six of its ninety-three entries are unreviewed and research grade, and grading a clinician against them silently would claim more authority than the catalog claims for itself.
Voice narration is experimental and hidden until you switch it on, and the dictation exception it needs against the signed-field policy is written but not signed off. Teaching Cases remain built, tested, and deliberately not in front of students—the hint ladder got a real workout this cycle and improved at both ends, but the regulatory brief still describes two AI archetypes rather than three, and that comes first. Chart's Domain Lens is on for development only: the button is hidden in packaged builds and a configuration switch closes the route regardless, pending the regulatory call its own carve-out plan defers to counsel, and its prompt still needs owner sign-off.
The resizable layout passed every automated gate—type checks, the full frontend suite, the production build, all eight design-system drift gates—but four of its sixteen acceptance criteria are about how a drag *feels* in a running app, and those are on an eighteen-item runtime checklist that has not been walked yet.
Expect one slow session open. Bumping the source producers and the vigilance staging artifact means any session warmed before this release recomputes those artifacts the first time you open it. Two of those recomputes change published output rather than merely refreshing it: source results move, sometimes substantially, and frontal-midline theta gating on eyes-closed recordings changes because it now uses the producer's own alert windows rather than a hardcoded stage set. Both are corrections. Chart is still a separate installation rather than a tab, findings still cross between the two products as a file you export and import, and ordering a recording from inside Chart still waits on the one piece that lives outside this repository.0.5.33—August 2026
Section titled “0.5.33—August 2026”What Launch Was Actually Waiting On
Section titled “What Launch Was Actually Waiting On”Every performance estimate going into this cycle was inference. There were no timing marks anywhere in the renderer and the API returned no timing headers, so “this part is probably the expensive one” was as far as anyone could honestly go. So the work opens by measuring rather than guessing: four marks across the boot path—module evaluation, plugins settled, first commit, first pane painted—and per-request timing on every trace-window response, split into cache lookup, compute, and serialization. Then the fixing started, and three things turned out to be sitting on the critical path of every launch with no business being there.
The 3D stack was the largest. The app pulled three.js, React Three Fiber, drei and niivue in as a namespace import so that runtime plugins could reach 3D, and a namespace import is opaque to the bundler—it cannot drop what it cannot prove is unused—so the entire 3D barrel was parsed before first paint whether or not you opened a 3D view that session. Plugins now ask for 3D when they want it, and it loads once. The second was the panes: each pane’s definition statically imported its own drawing code, so reading a pane’s label at bootstrap dragged in the pane and everything the pane imports. Components load on demand now; the definitions stay eager, because laying out the tab strip needs the labels. The third was the plainest of the three. The app rendered nothing at all until the backend answered a question about installed plugins, racing two fetches against a three-second timeout—and on a cold launch, when the backend is still coming up, neither fetch can win, so the full three seconds elapsed before anything mounted. It mounts immediately now and takes a late-arriving plugin in stride.
The entry bundle went from 6.5 MB to 3.1 MB, and from 1.9 MB to 0.9 MB compressed. Less than half of what it was.
A Third of a Second, Spent on Nothing
Section titled “A Third of a Second, Spent on Nothing”Every time you move the trace window, the backend hands the renderer a few megabytes of floating-point numbers, and most of the time it spent doing that went into a generic encoding pass that walked roughly 1.5 million floats one at a time in order to produce exactly the plain lists it had already been given. Measured end to end over a loopback socket on a 300-second nineteen-channel window, one montage went from 337.8 ms to 9.8 ms; all four montages together went from 1.1 seconds to 32 ms. The bytes on the wire are identical. This is a deleted step, not a changed format.
The bipolar montage had a second problem, and it reads as an oversight rather than a decision. Every other montage rounds to 0.1 µV before serializing—well below anything clinically readable, and worth tens of megabytes across a session—while bipolar alone shipped full float64 precision, which made it three times the size of average-reference on the same recording. Rounded like its siblings, it is now slightly smaller than average-reference, which is what a montage of its channel count should cost. Auto-gain is still measured on the unrounded signal, so nothing about how the trace looks has changed.
One proposed change was cancelled by its own measurement. Compressing responses on the wire reads as obviously right, and on a connection that never leaves the machine it is simply wrong—the bytes were never the constraint, and the compression cost real time. It was dropped rather than shipped on the strength of the intuition.
Every Few Beats, Counted Twice
Section titled “Every Few Beats, Counted Twice”On a slow resting heart, the app was inventing heartbeats. Beat detection enforced a fixed 400 ms refractory period after each beat, sized for the fastest plausible subject at 150 BPM. At 52 BPM the secondary wave that follows every beat—the T-wave on ECG, the dicrotic notch on a pulse channel—arrives around 577 ms, comfortably clear of that floor, and was counted as a heartbeat in its own right. Each phantom beat splits one true interval into two halves, and the successive differences that produces dominate every variability measure built on top of them. One real session reported an RMSSD of 205.8 ms and an SDNN of 237.0 against a truth of 61.5 and 61.4. The quality strip sitting beside those numbers read “valid beats 100%”, because a half-length interval falls comfortably inside the absolute plausibility window it was checking against.
Detection now scales its refractory period to the subject’s own rhythm instead of to the fastest subject imaginable: a seed pass estimates the prevailing interval, then peaks are re-detected at 0.6 times that median, iterating, since removing splits raises the median. Amplitude could not have solved this—on the real recording the phantom peaks were 91% as tall as the genuine ones. Validity gating is now shared between the two code paths that needed it and bounds intervals relative to the recording’s own median as well as absolutely, which is what makes a “valid beats” percentage mean anything. The corrected count and rate agree with an independent detector run over the same recording: 469 beats against its 471, 52.0 BPM against its 52.3. Sample entropy on that session rose from 0.25 to 0.95, which says the “reduced complexity” it had been reporting was measuring the injected beats. HRV artifacts recompute rather than being served from cache, so an affected recording will report new numbers the next time you open it—and a report you have already handed over carrying HRV on a slow-hearted recording is worth regenerating.
Both Conditions, Not Whichever Finished Last
Section titled “Both Conditions, Not Whichever Finished Last”Both the eyes-open and eyes-closed chains compute a source atlas, and both were filing their results under the same pair of names, so whichever chain finished last won and the other was silently discarded. The same defect sat in the meditation producer. Compounding it, the Exploratory phase collapsed every resting recording into a single view pinned to eyes-closed, with nothing on screen saying so—which means the eyes-open atlas was not merely overwritten, it was unreachable. Every emission is scoped to its condition now, and the phase has one tab per resting condition, labelled by condition. On a real session the two land as genuinely distinct results: 178 connected partners across 68 parcels with eyes open, 185 with eyes closed.
Two smaller corrections in the same area. The montage gate on the spectral atlas counted every channel in the file rather than only the EEG channels, so a recording carrying auxiliary channels was judged against an inflated count. And the phase-seven tab is now labelled “Plugins” rather than “Exploratory”, which is what it has actually been since it became the surface where installed add-ons render. The strip reads QEEG · EO, QEEG · EC, ERP, Plugins · EO, Plugins · EC, Report.
The Canvas Moves Out
Section titled “The Canvas Moves Out”Four releases have carried a note that the clinician-facing reasoning surfaces were being separated into their own product. That is done. Workstation boots with no Reasoning Canvas and no Synthesis tab, and in their place is a Report tab—the same report writer, reached directly rather than through the third phase of a reasoning program. The reason is a claim Workstation makes about itself: it is a research and educational instrument for the analysis and visualization of EEG. The canvas is not that. Framings are hypotheses about a person, the twelve consideration domains are a differential by another name, and marking a primary framing signs a conclusion. That is clinical reasoning, and it belongs to a product that says so.
Coherence Chart is that product, and it now runs as an application in its own right: it opens a clinic folder, creates and holds subjects, keeps its own findings, carries a settings surface for its own API key, and renders its own report from the shared engine with its own template and its own prose voice, stating its own intended use rather than borrowing Workstation’s. One change mattered more than any relocation—the canvas is keyed to the person rather than to a recording, because a chart can exist for someone who has never been recorded. The old gate requiring a completed visual read of a raw recording is gone for the same reason: nothing in Chart can produce one, so carrying that gate across would have locked the canvas permanently. Findings you mark in Workstation travel over as a file you export and import, merged by identity, so re-importing an edited session lands the upstream change while your own note on it survives verbatim. Chart also picked up what the canvas had been missing: a framing seeder whose citations you can check, framings that stop being labelled unsupported once your own attached evidence anchors them, an indicator that says which AI call is running rather than only that one is, and length limits on AI prose that trim an overrun instead of discarding the whole result.
Alongside the move, a set of labels that concluded more than the data does have been reworded. Red flags are deviation flags. The ERP verdicts are peak reads. A severity of “Critical—immediate clinical consideration” is a marked deviation. The glossary’s three strongest device-flavored claims are back in the register the literature actually uses. And the surviving descriptive AI feature is called “Describe the Data”, which pairs with “Ask the Data” and says exactly what it does.
A Case You Can Hand a Student
Section titled “A Case You Can Hand a Student”Teaching Cases are new. A clinician works a case the ordinary way, tags the findings that carry the lesson using the tags that already exist, and exports the whole thing as a .cwcase file that carries nothing identifying. The wizard shows its work before it writes anything: what is dropped entirely, listed by name and count—attachments, intake, labs, the subject record, the audit log; what is replaced—a pseudonym for the identifier, a synthetic date, normalized recording filenames, ages over 89 collapsed to 90+; and a per-field diff of every redacted span in the author’s own prose, editable in place. Then it shows every screenshot full size, one at a time, and will not let you skip or bulk-accept that step, because a PNG frozen at mark-time is the one thing no text pass can inspect. The bundle is built from an allowlist rather than a denylist, and that is the load-bearing decision: anything the profile does not explicitly permit does not travel. An adversarial test seeds identifiers into every prose field, filename and header and then scans every byte of the result—it earned its keep on the first run, catching the real date of service still riding along in the session record.
A student opens the case from the same import control that takes an ordinary bundle. The recording arrives with the author’s cleaning recipe already applied so the plots match, and everything else in the workspace behaves normally—clean, plot, mark findings. The author’s read stays locked. Each finding the student submits is judged as they mark it, and one that isn’t in the key is acknowledged rather than dismissed: they are told it isn’t part of this case’s key and may be worth raising with their supervisor, never that what they saw isn’t there. A stuck student can ask for a hint and gets a region, then a band or feature, then a near-answer, then the finding itself—which counts as recognized, is flagged as hinted, and costs them score rather than progress. Nobody is ever permanently stuck. When every required finding is recognized, the author’s full read unlocks. There is no praise anywhere in it; progress reads “3 of 5 key findings recognized.”
Redacted at the Door
Section titled “Redacted at the Door”A clinical document can carry a name, a date of birth, an MRN, a street address. Where should those be removed—at every point where a document might reach a model, or once, on the way in? The check that existed was three regular expressions with a single production caller, which logged its warnings and then handed the model the raw text five lines later, while the real redaction engine already sitting in the codebase had no callers on any Workstation AI path at all. No prompt had ever actually been redacted. Redaction now happens at import, after which every downstream reader is safe by construction: case history is stored as a redacted sidecar and only the sidecar is read, document extraction redacts before the extracting model sees anything, and the proposal fallback redacts before it embeds a document. Sessions imported before this are backfilled on first read, and if redaction fails the reader returns nothing rather than falling back to raw text. The engine is a base dependency now instead of an optional extra, which is the only defensible arrangement—a build that cannot redact must not ingest documents.
Known Issues
Section titled “Known Issues”Teaching Cases are built, tested, and deliberately not for students yet. The instructional AI refuses structurally to run against anything but a teaching bundle, and that fence is code and tests rather than policy—but no case should be put in front of a student until the regulatory brief is updated from two AI archetypes to three. Build all of it; hold the distribution.
One performance item was measured and then deliberately left alone. Focusing a pane re-renders 141 components, 81 of them panes, because they all consume one shared context value that an ordinary click invalidates. What is still missing is the only number that decides it: how long those re-renders actually take. Under a frame, the item should be cancelled outright; at 100 ms it is next in line. Timing it comes before touching the widest-blast-radius change in the plan.
Two notes for anyone running from source or writing plugins. The faster serializer is a new compiled dependency, so a stale environment needs a reinstall—the app logs the fix once and falls back to the old path rather than refusing to start, which is a correction to how this first landed on dev. And a plugin reaching 3D through the old global names now gets a named error pointing at the loader to call instead, while the 3D canvas component has moved off the SDK’s main entry to a deep import. In-repo consumers were caught and named by the compiler; a plugin loaded from outside this repository cannot be, so that one surfaces at runtime.
Chart is a separate installation, not a tab. Findings cross between the two products as a file you export and import rather than over a live connection, and ordering a recording from inside Chart is specced but not built—it needs its own login registered on the portal side, which is the one piece that lives outside this repository.
0.5.32—July 2026
Section titled “0.5.32—July 2026”The Comparison, Lifted Out
Section titled “The Comparison, Lifted Out”Where is the line between a tool that shows you a recording and a tool that tells you what the recording means? For Coherence Workstation the sharpest place that line blurred was the normative comparison—the surface that takes your recording’s spectra and connectivity, holds them against a reference database of “typical,” and reports how many standard deviations each channel sits from the norm. A z-score is a short step from a verdict. So the normative comparison has moved out of the workstation entirely.
It is now a separate, open-source add-on you install deliberately, or the workstation runs without it and says so plainly. The base application no longer carries the normative machinery at all—not the pipeline producers, not the connectivity math, not the head-map and spectra panes, not the reference-database dependency they leaned on. A test now guards the boundary: the workstation’s own code is forbidden from so much as importing the normative package, and the suite fails if it ever does. With nothing installed, the panes that once carried normative overlays fall back to their built-in views or state honestly that the comparison is absent. Nothing breaks in its absence. It simply isn’t there until you choose it.
A Plugin System That Names No Plugin
Section titled “A Plugin System That Names No Plugin”Lifting one feature out cleanly is only worth the effort if the seam left behind is general. The workstation already ran a runtime plugin system for its legacy “views”—folders dropped into a plugin directory, discovered by manifest, loaded without the app knowing their names at build time. That same discipline now reaches the deeper machinery: the pipeline producers that compute artifacts, the analysis panes that render them, and the data endpoints that serve them all arrive at runtime from whatever is installed, each described entirely by its own manifest. The host names no plugin anywhere in its code. It reads a folder, trusts the manifest, and mounts what it finds. Normative is simply the first feature to travel this path end to end, which is why it could be removed without leaving a hole where it used to be wired in.
Installing an Add-On
Section titled “Installing an Add-On”An add-on you cannot install from inside the app is an add-on nobody installs. Settings → Plugins is now a real install-and-manage surface rather than a read-only list. You can install a plugin straight from a Git repository by URL, or from a local folder through the native file picker, and the installed list shows each plugin with its contribution count—built-ins badged and protected, the ones you added removable behind a confirm. Because the backend cannot relaunch itself, any install or removal ends with a plain notice that a restart is needed before the change takes. This is opt-in by design: nothing is provisioned for you on startup, and the workstation ships knowing about no particular add-on.
Careful What Gets Installed
Section titled “Careful What Gets Installed”Code that installs more code from the internet has to be careful, and clinical software doubly so. Installs are contained to the user plugin directory, and plugin names crafted to escape it are rejected outright, so an install cannot write anywhere it does not belong. Cloned sources are cleaned up in a way that holds on Windows as well as macOS, where the naive delete had been failing. And the extraction of the normative code into its public repository was run through a history scrub that drops every dataset from the entire git history—the reference database travels as code, never as the clinical recordings it was built from—behind a gate that fails closed rather than open if it cannot prove the history is clean. Say plainly what an open repository contains, then make the tooling prove it.
Known Issues
Section titled “Known Issues”Installing or removing a plugin needs a restart—the backend cannot relaunch itself in place, so the change lands on the next launch, and the app tells you as much rather than pretending otherwise. Installation is deliberately manual: there is no auto-provisioning, so the normative add-on is something you go and fetch by URL or folder, not something that appears on its own. And this is the same repositioning noted the last two releases, now carried into the architecture rather than only the copy. The normative comparison lives outside the workstation as its own installable thing, and the workstation holds no opinion it did not earn from the recording in front of it. The separation of the clinician-facing reasoning surfaces continues along the same line.
One flag from the cross-reference (step 2), since your notes and the commits disagree. Your highlight was “Performance fixes and reporting v1,” but every commit since the 0.5.31 changelog entry (e5a1d1396..HEAD) is the normative-plugin extraction workstream — there’s no performance or reporting work in this range. The performance and reporting-v1 story is already written up in the existing 0.5.28 and 0.5.31 sections. So I wrote 0.5.32 about what actually changed (the extraction), not the note. Two things to double-check on your end: (1) whether 0.5.28/0.5.31 were meant to ship as their own tags or fold into 0.5.32 — they’re in the changelog but were never tagged (last tag is v0.5.27); and (2) that describing the normative reference database as “open-source” is accurate for how you’re distributing the normative-plugin repo publicly. If either is off, tell me and I’ll adjust.
0.5.32—July 2026
Section titled “0.5.32—July 2026”The Comparison, Lifted Out
Section titled “The Comparison, Lifted Out”Where is the line between a tool that shows you a recording and a tool that tells you what the recording means? For Coherence Workstation the sharpest place that line blurred was the normative comparison—the surface that takes your recording’s spectra and connectivity, holds them against a reference database of “typical,” and reports how many standard deviations each channel sits from the norm. A z-score is a short step from a verdict. So the normative comparison has moved out of the workstation entirely.
It is now a separate, open-source add-on you install deliberately, or the workstation runs without it and says so plainly. The base application no longer carries the normative machinery at all—not the pipeline producers, not the connectivity math, not the head-map and spectra panes, not the reference-database dependency they leaned on. A test now guards the boundary: the workstation’s own code is forbidden from so much as importing the normative package, and the suite fails if it ever does. With nothing installed, the panes that once carried normative overlays fall back to their built-in views or state honestly that the comparison is absent. Nothing breaks in its absence. It simply isn’t there until you choose it.
A Plugin System That Names No Plugin
Section titled “A Plugin System That Names No Plugin”Lifting one feature out cleanly is only worth the effort if the seam left behind is general. The workstation already ran a runtime plugin system for its legacy “views”—folders dropped into a plugin directory, discovered by manifest, loaded without the app knowing their names at build time. That same discipline now reaches the deeper machinery: the pipeline producers that compute artifacts, the analysis panes that render them, and the data endpoints that serve them all arrive at runtime from whatever is installed, each described entirely by its own manifest. The host names no plugin anywhere in its code. It reads a folder, trusts the manifest, and mounts what it finds. Normative is simply the first feature to travel this path end to end, which is why it could be removed without leaving a hole where it used to be wired in.
Installing an Add-On
Section titled “Installing an Add-On”An add-on you cannot install from inside the app is an add-on nobody installs. Settings → Plugins is now a real install-and-manage surface rather than a read-only list. You can install a plugin straight from a Git repository by URL, or from a local folder through the native file picker, and the installed list shows each plugin with its contribution count—built-ins badged and protected, the ones you added removable behind a confirm. Because the backend cannot relaunch itself, any install or removal ends with a plain notice that a restart is needed before the change takes. This is opt-in by design: nothing is provisioned for you on startup, and the workstation ships knowing about no particular add-on.
Careful What Gets Installed
Section titled “Careful What Gets Installed”Code that installs more code from the internet has to be careful, and clinical software doubly so. Installs are contained to the user plugin directory, and plugin names crafted to escape it are rejected outright, so an install cannot write anywhere it does not belong. Cloned sources are cleaned up in a way that holds on Windows as well as macOS, where the naive delete had been failing. And the extraction of the normative code into its public repository was run through a history scrub that drops every dataset from the entire git history—the reference database travels as code, never as the clinical recordings it was built from—behind a gate that fails closed rather than open if it cannot prove the history is clean. Say plainly what an open repository contains, then make the tooling prove it.
Known Issues
Section titled “Known Issues”Installing or removing a plugin needs a restart—the backend cannot relaunch itself in place, so the change lands on the next launch, and the app tells you as much rather than pretending otherwise. Installation is deliberately manual: there is no auto-provisioning, so the normative add-on is something you go and fetch by URL or folder, not something that appears on its own. And this is the same repositioning noted the last two releases, now carried into the architecture rather than only the copy. The normative comparison lives outside the workstation as its own installable thing, and the workstation holds no opinion it did not earn from the recording in front of it. The separation of the clinician-facing reasoning surfaces continues along the same line.
0.5.31—July 2026
Section titled “0.5.31—July 2026”Switching Conditions Without Starting Over
Section titled “Switching Conditions Without Starting Over”Have you ever cleaned an eyes-open recording, switched to eyes-closed to check one thing, and switched back only to watch the trace you were just reading rebuild itself from nothing? The signal never left memory—not one byte was re-fetched—and yet the pane blinked, the plot tore itself down, the Clean panel reloaded. The work was all still there. It simply wasn’t being held.
The cause was mundane and exact: an inactive condition tab renders hidden, and a hidden pane reports its size as zero by zero. Committing that zero size nulled the plot and destroyed the live chart, so returning to the tab rebuilt the entire trace even though the buffer behind it was untouched. Switching now keeps the chart alive across the hand-off, ignoring the spurious zero-size report, and serves the resident buffer straight back on return. The op-stack stays resident rather than re-hydrating, the cleaning state is cached per condition so the Clean panel no longer reloads on every switch, and each condition’s trace and state are prefetched in the background—so the switch you are about to make is often already warm before you make it. Moving between eyes-open, eyes-closed, and ERP should feel like turning your head, not reloading a page.
Work the App Refuses to Repeat
Section titled “Work the App Refuses to Repeat”Speed is partly about not tearing things down and partly about not computing the same thing twice. Two recomputations that had been quietly running on every request are now cached to disk. The spectral-model fit—the FOOOF decomposition that separates the rhythmic peaks in a power spectrum from the sloping background beneath them—was refit each time, even though its input, the spectrum itself, was already stable and cached; it now persists as a compressed fit keyed to the exact op-stack state, so it recomputes precisely when you change the cleaning and not otherwise. The canonical display substrate—the cleaned signal every pane draws from—is likewise written to disk and prewarmed for your primary condition, and a corrupt cache file degrades to a plain miss rather than raising, so a bad sidecar can never poison a read.
The background ICA warming got its own lane, too. On session-open the app pre-fits every condition’s decomposition, and that fit is expensive—ten to sixty seconds of Picard. It had been sharing the same execution pool as the foreground requests, so a warm in flight could hold every slot and freeze the very condition switch you were trying to make; worse, it delayed the cheap sign-off check queued behind it, so a signed recording could briefly read as unsigned. The warm now runs on a small dedicated pool that leaves the foreground room to breathe, and the sign-off gate no longer lies while a load is in progress. And because a warming recording used to look identical to a finished one, there is now a plain “preparing” banner at the top of the trace while the fit is still coming up—especially useful for ERP, whose warm is deferred until last.
The Report You Hand Over, Reframed
Section titled “The Report You Hand Over, Reframed”The client-facing report took its clearest step yet toward being exactly what it claims to be: a descriptive, research-use document, not a clinical verdict dressed as one. The prose throughout—both the fixed copy and the AI-drafted passages—now speaks in a plainly descriptive, third-person register. The findings you mark render as fixed screenshots rather than live, re-rendering components, so the report is a stable artifact that reads the same today and six months from now. The ERP section is gone from the client report, which was never the right audience for it. A new “About This Recording” card states the technical facts of the session—date, the eyes-open and eyes-closed conditions and their durations, any ERP paradigms, sample rate, channel montage, equipment—and omits any row it cannot verify from the recording itself, so nothing on it is invented. The redundant “Signed Reading Summary” is dropped in favor of the single Overall Descriptive Summary, renamed from “Integrated Pattern Summary,” and the disclosure is consolidated into one “About This Report” section that states, in bold, how AI was used and that a clinician reviewed the result. Say plainly what a document is, and it needs no defending.
Editing That Doesn’t Fight You
Section titled “Editing That Doesn’t Fight You”A report you cannot easily edit is a report you will not trust. Several rough edges in the authoring flow are smoothed. Empty sections are now genuinely editable rather than looking editable and doing nothing, with a scope note where one helps. An auto-generated section becomes clinician-authored the moment you edit it—the tool stops treating your words as its draft and starts treating them as yours. Leaving the preview to return to the editor is now an obvious control rather than a guess, and Escape does it too. Findings captured from the raw trace burn the selection box you drew straight into the screenshot, so the report shows what you framed rather than the bare trace, and the mark-finding dialog previews that same box before you commit it. And the report’s light-and-dark theme toggle now attaches regardless of when its script happens to run, rather than sitting inert when the page loaded a beat too fast.
One Fewer Machine-Made Label
Section titled “One Fewer Machine-Made Label”Continuing the repositioning from last release, the automated phenotype classifier is gone—the pipeline stage that assigned named conditions, the server producer and fallback behind it, the frontend pane with its navigation and glossary entries, and the AI section that narrated it. A tool that describes what a recording shows should not also hand you a category to file it under; that inference belongs to the clinician, not the software. The signal features the classifier once leaned on remain, surfaced descriptively where they are earned—most visibly in the vigilance trajectory—rather than compressed into a label. This is the workstation continuing to become what it says it is.
Known Issues
Section titled “Known Issues”The background prefetch that keeps each condition resident is what makes switching feel instant, and it is deliberately shipped as an experiment: holding every visited condition’s trace and cleaning state in memory trades space for speed, and on very long, multi-condition recordings that footprint is still being tuned—tell me if you watch memory climb on a marathon session. And the larger repositioning is still only partly landed. The descriptive report voice, the intended-use framing, and the phenotype removal are in this build; the separation of the clinician-facing reasoning surfaces into their own product continues.
0.5.28—July 2026
Section titled “0.5.28—July 2026”The Drag You Felt Wasn’t Missing Work
Section titled “The Drag You Felt Wasn’t Missing Work”If ICA removal and condition switching had started to feel heavier than they used to—the fan climbing on session-open, a pane sitting a beat too long before it painted—you weren’t imagining it, and the cause was not the obvious one. We audited the major flows expecting to find precompute that had quietly fallen out. The opposite was true: the eager machinery is more built-out than it has ever been—parallel ICA prefit, a producer warm-bus, sign-off precompute, three tiers of content-addressed cache. The slowness was contention and sequencing, not absence. ICA warming had drifted behind the hydration handshake instead of firing at true session-open; the bus issued a fan-out of identical fetches with no de-duplication, so a dozen panes asking for one artifact became a dozen requests; the source beamformer was recomputed independently by every network pane; the in-memory caches were small enough that switching conditions thrashed to disk; and heavy compute ran on an unbounded pool that let the session-open pile-up saturate every core at once.
Five changes fix it, each built on the machinery already there rather than bolted beside it. ICA warming starts at session-open again. Identical in-flight bus reads collapse to a single request. The network panes share one beamformer pass instead of paying for it three or four times over. The caches are sized to hold every condition and a couple of baselines without evicting. And heavy work runs on one bounded executor that leaves the event loop room to breathe. Nothing new was invented—the work simply stopped fighting itself.
One ICA, Not Two
Section titled “One ICA, Not Two”Speed was only half the ICA story this cycle; the other half was making the cleaning agree with itself. A handful of ways it could quietly disagree are now closed. The decomposition algorithm is persisted per recording, so the fit warmed in the background and the fit you clean against can no longer be computed two different ways—and the ERP path resolves to the same algorithm the backend actually runs, rather than a stale frontend default. Switching conditions no longer flashes the previous condition’s rejected components before the new ones load, and the status pills stop refitting under the wrong substrate when you navigate. The Settings dropdown no longer silently overwrites your workspace default when you open it. And when a fit goes stale after you’ve signed off, the sign-off is preserved and flagged for re-review rather than silently discarded—analysis pauses, tells you why, and hands you the path back, instead of pretending nothing changed.
Is This Recording Even Interpretable?
Section titled “Is This Recording Even Interpretable?”Before you read a single spectrum, there is a prior question: can this recording be trusted to carry a read at all? Vigilance—the drift of arousal from alert wakefulness down toward drowsiness across a session—is what answers it, and the whole-session view of it is back, now as its own phase in both the eyes-open and eyes-closed resting flows. You get the full-session staging timeline, a quality verdict that says plainly whether the recording is interpretable, a four-panel feature trajectory that shows why it staged the way it did—including the anteriorisation of alpha as vigilance falls—and a set of reflection prompts. It sits right after cleaning and before the spectral read, which is exactly where the “should I trust what comes next” checkpoint belongs.
Made to Match EEGLAB
Section titled “Made to Match EEGLAB”If you move between this tool and EEGLAB, the time-frequency views now speak the same visual language. The Dynamics heatmaps and the scalp maps default to the Classic colormap—the EEGLAB jet—and event-related spectral perturbation renders in decibels, the unit you already read it in. The head-butterfly lays its traces on true scalp coordinates rather than a grid, so a channel sits where the electrode sat, and the ERP-image defaults to trial order with a P300-sort toggle for when you want to watch latency line up. Small things individually; together they mean less time translating between two pictures of the same data.
What This Tool Claims, and What It Doesn’t
Section titled “What This Tool Claims, and What It Doesn’t”Coherence Workstation now states its intended use in plain sight, on the welcome screen and in the license: a research and educational instrument for the analysis and visualization of EEG and ERP data, used by qualified professionals who bring their own judgment. That sentence is not throat-clearing—it is the line the whole product is being brought into alignment with. The AI no longer wears a clinical-colleague persona. It has two jobs now, both strictly descriptive: narrate the findings you have already verified into readable prose, and answer questions about the recording in front of you. It does not suggest, prescribe, or introduce clinical content you didn’t author, and that constraint is enforced rather than merely intended. The generated report reads in the third person and carries a research-use disclosure. The clinician-facing reasoning surfaces are being carved out into a separate product, continuing the direction noted last release; this release is the workstation settling firmly into what it is.
Known Issues
Section titled “Known Issues”The repositioning above is partly landed—the intended-use statement, the report reframe, and the AI’s descriptive-only constraint are in this build, while the larger separation of the reasoning surfaces into their own product is still in progress. And one caution worth stating plainly about vigilance: the staging model is calibrated on typical resting EEG, so on atypical morphology it can mis-stage. That is exactly why the feature-trajectory chart sits next to the verdict—read the mechanism, not just the label, and tell me where the two disagree.
0.5.27—July 2026
Section titled “0.5.27—July 2026”A Gate You Never Chose
Section titled “A Gate You Never Chose”Out of the box, Surface ERP—the phase that used to be called the cold read—was quietly required. You could clean your recording, sign off your work, and still find Next refusing to move you forward, with a note to “finish the cold-read primaries.” Nothing was broken, but nothing looked right either: a gate you never opted into reads exactly like an app that’s stuck. The cold read is now optional by default. If you want it enforced, the toggle is still there under Settings → Pipeline; the difference is that the requirement is now something you turn on rather than something you have to discover and turn off. One scope note worth saying plainly: the same preference also governs whether server-side AI synthesis and the Reasoning Canvas wait on the cold read, so out of the box both are un-gated as well. That is deliberate, and flagged for clinical review.
The Spinning, Stopped
Section titled “The Spinning, Stopped”If you signed off a condition and then watched the app start to churn—the fan climbing, the backend chattering to itself for minutes on end—this was the cause. When a condition is signed off, the app lifts its fetch gate for every analysis artifact at once, and a warmed session legitimately has many artifacts that will never arrive: HRV with no ECG behind it, a meditation view with no paradigm, the unscoped fallback leg of a scoped read. Each of those absent artifacts was being polled once every five seconds for the better part of eight minutes—dozens of them at once, all returning nothing. The result was a sustained request storm that pegged the CPU and buried the backend in 404s. The polling now backs off to one attempt every twenty seconds and gives up after thirty tries instead of a hundred, which cuts the steady-state traffic by roughly four times while holding the same eight-minute recovery window—a genuinely late artifact still lands on its own, no manual reload required.
0.5.26—July 2026
Section titled “0.5.26—July 2026”This one is for Nicky and Justin. A great deal has moved since the proof of concept I shared in March—enough that 0.5.26 feels less like a point release and more like the app growing into the shape it was first sketched in. The headline is simple: the thing 0.5.0 promised but couldn’t yet deliver, a real report you can hand to a client, is here.
Everything below is what I most want the two of you to push on. Work the data, mark what you see, and tell me where it holds and where it breaks.
The Report You Can Actually Hand Someone
Section titled “The Report You Can Actually Hand Someone”When I wrote the first release notes, the “Mark Finding” workflow collected evidence but had nowhere to put it. That gap is closed. You work the recording, mark your findings as you go, leave your clinical determination and the role each finding plays, and the report comes out printable, emailable, and substantive. Each finding renders as a proper card now—carrying its screenshot, the role you assigned it, and your determination—rather than the flat one-line-per-finding list it used to collapse into. The report hydrates the clinic’s own identity onto the cover and signature, reports the measured QEEG values as a quantitative background section, and follows the IQCB reporting spine section by section. A “storyteller” pass can narrate the signed Chain of Reasoning into plain client-facing prose, and the optional AI polish is readability-only—the deterministic draft is always the source of truth and the fallback, so if you want the plain text, leave the polish off.
The goal I set for myself was that the first clear return on this tool should be how little friction sits between working the data and having a report you would actually send. Judge it against that.
The Reasoning Canvas
Section titled “The Reasoning Canvas”Marking findings tells you what you saw. The Reasoning Canvas is where you decide what they mean, together. It implements Analysis of Competing Hypotheses—a structured technique developed for intelligence analysis and now used anywhere reasoning has to survive its own confirmation bias. You lay out competing framings, attach each observation as either supporting or counter, and watch how the weight actually distributes. When one framing earns it, you mark it as your primary framing. That act organizes your thinking; it does not draw a conclusion for you. The Synthesis intake and the Reasoning screens both got a full design pass this cycle, and quick-lane seeding is now scoped to evidence that is genuinely grounded in the EEG rather than inferred around it.
Note that the Reasoning Canvas will be moving out into a separate product soon.
A New Look, On by Default
Section titled “A New Look, On by Default”The redesign is no longer an experiment you have to opt into—it renders out of the box. Most panes have been brought onto the new design system: the head-map suite, the normative and connectivity surfaces, the ERP and phenotype views, the EULA and settings, and the Ask AI pane. A few surfaces are still being carried across, so if something looks wrong, the old interface is one keystroke away at Cmd+Shift+D, or under Settings → Experiments. I would rather you drop back and tell me what looked off than work around it.
Files That Don’t Cooperate
Section titled “Files That Don’t Cooperate”Real recordings are messier than test fixtures, and the old import path trusted files further than it should have. Import now sniffs the true format from the raw bytes rather than the extension, then runs each file through roughly twenty EDF and XDF validation checks—truncated final records, discontinuous timelines, dummy channels, duplicate or non-standard labels, headers that misreport their own byte counts, streams that sent no samples or arrived with non-monotonic timestamps. Before anything is imported, you see a per-file triage card telling you exactly what it found. Files it can safely repair, it repairs, and it records what it did; files that are genuinely unrecoverable it hard-blocks rather than letting them quietly poison a session. Most of the depth is on the EDF side, because that is where the malformed headers actually live.
Built for a Shared Folder
Section titled “Built for a Shared Folder”Many users keep their recordings in a shared Dropbox folder, and two people opening the same folder—or the same session—was a quiet way to corrupt state. That is handled now. There is an advisory lock on the clinic folder and a lock on the individual session, a banner when a session is already open somewhere else, and a toast if a second copy of the app is running against the same folder. A folder-health panel in Settings scans the clinic folder and reports what it finds. And when it is time to capture a new recording, there is a “Record session” hand-off to Coherence Recorder, which now lives as its own dedicated application.
ERP Learns to Watch Time
Section titled “ERP Learns to Watch Time”An evoked response used to be a single snapshot. The new Dynamics phase gives it a time axis. It is a time-frequency suite that shows how spectral power and phase-locking evolve across the epoch—event-related spectral perturbation, meaning how power rises and falls after the stimulus, and inter-trial coherence, meaning how consistently the phase lines up from one trial to the next—laid out as heatmaps over the head. An ERP-image view stacks the individual trials so latency jitter becomes something you can see rather than infer. The phase formerly called “Cold Read” is now “Surface ERP,” which is simply what it always was.
Known Issues
Section titled “Known Issues”The redesign is the default, but a handful of surfaces are still mid-migration; if a pane renders oddly, Cmd+Shift+D returns you to the old interface, and a note about which pane would help me a great deal. Windows had a real hardening pass this cycle—several long-standing CI failures repaired and a retry added for a transient file-write error—but the Mac build remains the most exercised, so Windows is where I most expect surprises. And the report’s optional AI polish, while disclosed and capped, is exactly that—optional; the deterministic draft it sits on top of is always what the report falls back to.
0.5.0—March 2026
Section titled “0.5.0—March 2026”This is a proof of concept, shared first with close friends. Coherence Workstation is a desktop application for QEEG analysis and interpretation—built to support both the daily work of neurotherapy practitioners and the longer research arc of The Quantified Soul. Everything in this release is new, because this is the beginning. What follows is an overview of what’s here, what works, and where the edges are still rough.
Platform and File Support
Section titled “Platform and File Support”Coherence Workstation runs on both macOS and Windows. The Mac build has seen significantly more interactive testing at this point, so that’s where things will feel most polished. The application handles a wide variety of NFX and EDF files—I built a thorough testing pipeline against every recording I could collect and get my hands on. You may find an edge case that slips through, but the foundation is solid.
The Processing Pipeline
Section titled “The Processing Pipeline”Running the full pipeline—eyes open, eyes closed, and ERP—takes roughly ten minutes on a modern Mac. That includes preprocessing, spectral analysis, connectivity, ICA decomposition, and everything else the system computes. ERP processing has been tested with the visual Oddball paradigm; auditory Oddball should work as well, though it hasn’t been put through the same level of hands-on verification yet.
AI Research Assistant
Section titled “AI Research Assistant”The AI Research Assistant is live across every analysis stage. Look for the “Ask AI” button as you move through the report. This is the feature that will receive the most development investment going forward—what you’re seeing now is early, and the quality of its observations will improve substantially as the prompts and clinical reasoning layer mature. But it’s functional, it’s useful, and I want you to push on it.
AI responses are optimized for Claude and require a Claude API token. It may be possible to use a subscription-based Anthropic account as well, though API access is the tested path.
Clinical Documents and PHI Redaction
Section titled “Clinical Documents and PHI Redaction”You can attach lab reports or neuropsychological evaluations to a subject’s record under “Clinical Documents.” The system will redact personally identifiable health information automatically—try it and see how it handles the documents you work with. This is an early capability, but it’s functional and worth exploring.
The “Mark Finding” Workflow
Section titled “The “Mark Finding” Workflow”This is the beginning of something I’m particularly excited about. As you move through the analysis, you can mark specific findings—flagging evidence as you encounter it. The intention is that these collected findings will eventually feed into a synthesized, interactive, stand-alone report suitable for sharing with clients. That synthesis layer isn’t complete yet, but the evidence-collection workflow is live and worth getting comfortable with now.
Crash Reporting and Privacy
Section titled “Crash Reporting and Privacy”Crash reporting is enabled, and the system is also configured to send non-fatal errors back to me. Per the disclaimer you’ll see on first launch, these reports contain only runtime data. All file names—including subject names—are redacted before anything leaves your machine. EEG data is never transmitted. I need to see what breaks, but I have no interest in seeing who you’re working with.
What’s Not Here Yet
Section titled “What’s Not Here Yet”The AI Research Assistant is available everywhere but still early—expect the depth and precision of its observations to evolve significantly. The “Mark Finding” workflow collects evidence but doesn’t yet synthesize it into a client-facing report—that’s a major upcoming milestone. Auditory ERP paradigms should work but haven’t been tested as rigorously as visual Oddball. And Windows, while supported, hasn’t had the same depth of interactive testing as macOS. If you hit something odd on Windows, let me know—that’s exactly why I’m sharing this now.