Monghoul 1.9.0
New
- Lifetime licences include every future update — A lifetime licence used to cover two years of updates from the date of purchase, after which the app would not activate on newer builds. That limit is gone, retroactively: every lifetime licence, however old, receives all future versions with nothing to renew.
- Passwords you would rather not store — Username/password and SCRAM connections have a "Save password" checkbox. With it off nothing is written to disk: Monghoul asks the first time you connect and remembers it until you quit, so one prompt covers every query, sync and expand for the session.
- Atlas service accounts — Atlas is moving from project API keys to programmatic service accounts, and a project that had already turned keys off could not connect at all. The credential dialog now takes either, and warns within thirty days of a service-account secret's expiry.
- Atlas users can be scoped to a cluster — An Atlas database user can be restricted to specific clusters, data lakes or streams from the user form; leaving the list empty means the whole project, which is Atlas's own default.
- What a role can actually do, and who has it — Selecting a role opens a pane with its effective privileges, flattened across the whole inheritance chain and marked as granted here or inherited, so a role assembled out of built-ins finally reads as something. Underneath it, the reverse lookup nobody could do before: which users hold the role, and which other roles inherit it.
- The shell command behind the form — Create User and Create Role show the equivalent
mongoshcommand, live off whatever is in the form and copyable. The password ispasswordPrompt()rather than the one you typed, so the clipboard never holds a credential. - Current operations — The footer's task indicator now lists everything the app is doing — queries, explains and previews, connection attempts, statistics reads, sidebar refreshes and inline edits, alongside exports, imports, copies and generation — each labelled with its kind and connection, and cancellable where the work can be stopped.
- Zoom in the status bar — Zoom was keyboard- and scroll-only and invisible after the first tip, so a window that came back at 80% just looked wrong. The status bar carries the percentage now, and clicking it opens step buttons, presets from 50% to 200%, and a reset.
- Rename a favourite — A favourite could only be renamed by opening it, editing the tab title and saving over it. Hovering a favourite in the sidebar now shows a rename button beside the remove one.
- Arctic Light — A new built-in theme: cool blue-gray with steel-teal accents, the light counterpart to Midnight Ocean, for people who find the cream-toned light themes too warm.
Improved
- Exporting results asks one question instead of six — The menu listed JSON, CSV and Excel twice, once under each scope heading. Pick the scope on a one-line toggle, then a format from a single list, with a line underneath saying what that combination will actually write — including that "All matching" re-runs the query with your
.limit(),.skip()and the result cap dropped. - The aggregation builder's Generate Schema banner appears in builder mode — Its stage editors read the same collection schemas the code editor's autocomplete does — field pickers,
$groupkeys,$projectpaths — so a missing schema limits the builder in the same way, and the banner's Generate button was the only way to ask for one from the editor panel. It was shown in code mode only. Alongside it, the Generated Code toggle no longer stays highlighted at a width too narrow to show the panel: it is disabled there, and says why. - A folded aggregation stage says what it does — Collapsed cards read the operator's generic description, identical for every
$matchin the pipeline; they show the stage's own body now, so a folded pipeline can be scanned. A one-line stage also takes one line of room rather than four. - A failed stage preview leads with the cause — MongoDB wraps the part you need in machinery and the preview printed the whole chain on one clipped line, so the half that says what is wrong was usually off the right edge. The root cause is the headline now, with the original message one click away, and the misspelt-stage, memory-limit, timeout and permission cases each get a line saying what to do about it.
- A syntax error reads as part of the stage — It was small red text floating in the editor's padding, truncated, recoverable only by hovering; it is a tinted strip flush with the card now, and it shows on a collapsed stage too.
- A markdown tab opens in the editor — An empty note opened in preview, which renders nothing, so every note began with a trip to the Edit button. A tab that arrives with content still opens in preview.
- A note behaves like a tab, not like a lesser one — Markdown tabs already carried working favourite buttons, but
Ctrl/Cmd+Shift+Sdid nothing on one and "Clone Tab" was not offered. Both work now;Ctrl/Cmd+Dcorrespondingly stops cloning a cluster monitor, which the menu never offered either. - The sidebar filter's match count counts what it highlighted — Searching for a connection name reported
0matches beside a tree that was plainly showing the connection, and searching for a database name reported that database's whole collection list — 50 matches for one hit — because matching a database reveals all of its children. - The Operation Logs collection filter lists every collection — It was built from the rows currently loaded, so a collection that first appeared on page 3 could not be filtered for from page 1. It also narrows with the other filters, and matches the collection you picked rather than every name containing it.
Performance
- A tab's results appear as soon as the tab does — Opening the app fetched 7.4 MB of JavaScript before it could draw a single row, and none of it started loading until the workspace had finished. A tab now needs 213 KB to show its results and starts fetching them while the workspace is still loading; the editor and the charting library follow in the background.
- A faster, smaller backend — The local server every query goes through starts in about 190 ms instead of 270 ms, and settles roughly 30 MB smaller.
- The aggregation builder's parser leaves the startup path — The JavaScript parser the builder uses to work out which fields each stage sees was loaded before the window could paint, on every launch, whether or not you opened the builder.
- A large generated-document schema stops re-rendering on every keystroke.
- A large export stops holding the whole file in memory — The writers pushed every document at the file as fast as the cursor produced them and never waited for the disk to keep up, so the backlog piled up in memory: a 200,000-document JSON export peaked at 599 MB of it. It peaks at 150 MB now, and an export is not reported finished until the file on disk is closed.
Interface
- Every built-in theme meets WCAG AA, and cannot silently drift below it again — An audit of all eleven themes found a long tail of colour pairs below the accessibility floor, the worst of them the dimmest text tier that carries most of the app's small labels, so field labels, metric captions and section headings were genuinely hard to read rather than just subtle. Corrections adjust lightness only, so each theme keeps its own hue and character, and a new test measures every theme on every run.
- A new Accent colour, so a PRO badge stops looking like an alert — The licence badge, upgrade badge and tier chip used the same blue as "new activity", so in the footer the PRO badge sat next to the MCP unread dot in exactly that colour. They use a tunable Accent colour now, and "Enabled" states stop using success green, which claimed an outcome that never happened.
- The schema sample sizes the free tier cannot use are shown as such — The 1K, 5K and 10K chips stayed clickable and quietly selected 500 instead, so no chip lit up and nothing said why. They are disabled now, and say what they need.
- Three theme colours that did nothing now do — The action, focus and pressed colours were settable in the theme editor and read by nothing; they drive filled buttons, the focus ring and the held-down state.
- The page-size field says what it accepts, and clamps instead of ignoring you — It took anything up to 10,000 — ten times the largest preset, and enough rows to make the view crawl — and silently reverted anything outside that range to the previous size, which reads as a field that ignored what you typed. A number above the ceiling is now clamped to it, and the clamped number is what the field shows.
- The plan comparison stops listing two free features as Pro — Multi-panel layout and sidebar folders are available on the free tier and were in the Pro column of the upgrade dialog's table. They are gone from it, and user and role management — which is genuinely Pro and was missing — is in.
- Text fields have a visible edge — An unfocused input, select or checkbox had a border too faint to meet the accessibility floor on any theme, over a fill near-identical to the page behind it.
- Small labels are cased by what they are, and stop mangling your data — Every small label was set in lowercase, which reads as a typo where the label is really a heading; headings above a group are small caps now, and the sidebar's section titles read as headings rather than dimmed lowercase monospace. More importantly, several of these were not labels at all and were being case-folded with the rest — aggregation stage names, your own
$facetbranch names, BSON type names, your connection names and theCOLLSCANmarker all render verbatim now. - Colours stop coming from the wrong palette — The aggregation builder's stage families were coloured from your chart series colours, so re-theming charts re-coloured the builder and in the default theme made every
$outand$mergeread as an error; chart field-type badges had it in reverse, so "Date" appeared as a success and "Boolean" as a warning. Both use the MongoDB value-type colours now, and an index's descending toggle stops turning the app's warning amber. - A failed insert or update is not reported in green — The result panel for Insert One, Insert Many and Update painted its success accent unconditionally, so an operation whose own subtitle read "Operation Failed" arrived in success green.
- Pickers and menus stay where you can reach them — Colour and font pickers opened off the bottom or top of the window, a colour picker stayed put while the row it belonged to scrolled away, and "Add stage" could flip above its button and off the top of a short window.
- The JSON viewer and the sidebar's spinner follow your theme — Both had colours hardcoded outside the theme system: the viewer's frame was fixed dark grey, unreadable under the light themes, and the spinner was a lime green that appears nowhere else.
- A query error looks like part of the result panel — When a query failed, the header that replaced the result header was taller and a different shade of grey, and set the error icon in a red tinted tile right beside a differently tinted error-type pill, so the panel visibly changed shape on the way to reading the message. It is the same band as the result header now, with a plain icon, and a long error scrolls inside the panel instead of stretching it.
Accessibility
- The connections tree announces itself as a tree — It has behaved like one from the keyboard for a long time, but every row was an anonymous container, so a screen reader could not say what it was on, how deep it was, or whether it could open. Rows are tree items now, with their depth, expanded state and selection.
- The result table announces its rows, columns and sort order — The app's main data surface said nothing about itself; it is a grid now, with each row's number and selected state and each cell's column. Four mouse-only affordances came with it: the row tick box, the search in the row-number header, sorting by clicking a header, and the nested viewer's "show more".
- Form labels name the field they sit above — Visible labels were decorative captions not attached to the control beneath them, so a screen reader read the input as an unnamed box and clicking the label did nothing.
- Click-only controls are buttons — Favourite and pinned-collection rows, sidebar section headers and folder rows, the tab strip's close ✕, the licence copy links, help bubbles, a theme's delete control and whole menu rows were plain containers with a click handler: unreachable without a mouse and unannounced.
- Dividers move from the keyboard — The query tree's Key/Value divider, the list/detail split in Operation Logs and Snippets, and the document preview's divider were drag-only; arrows move them, Shift jumps, Home and End go to the ends, and each reports where it is.
- Landmarks and regions — The workspace, sidebar and status bar are landmarks a screen reader can jump to, the drawers, theme editor and plan view are real regions, and the get-started screen is main content rather than navigation. The window also declares its language, so the interface is announced in English rather than whatever voice the reader defaulted to.
- Dialogs, dropdowns and menus announce themselves — Every "are you sure" was an unnamed box, giving no indication a dialog had opened or what it was asking, and every
Selectrendered its options as anonymous rows. A menu item's shortcut is also introduced as one, so "Copy Ctrl+C" is no longer read as a single phrase. - Decorative graphics stop being announced, and a loading spinner starts being — Spinners, the animated logo, expand chevrons and the arrow between explain stages were read out as unlabelled images; the loading indicator they sit inside says "Loading" instead, which nothing did before.
- The connections tree can be reached, and opened, from the keyboard — Tabbing into it landed on an invisible per-row drop target rather than the tree, and the tree itself showed no cursor on arrival, so the arrow keys appeared to do nothing and there was no way to expand a connection without a mouse. Tab now puts the cursor on a row — the one you last opened, or the first — with the arrow keys expanding and collapsing from there, and Enter opens a collection's indexes and schema, which previously only the chevron could do.
- The sidebar's hover preview follows the keyboard — Walking the tree with the arrow keys showed nothing about the row you were on: the preview card, with its size and index counts, only appeared under the pointer.
- Fit Column Width is in the column menu — Auto-sizing a column was only possible by double-clicking the thin line between two headers, so it was invisible unless you found it by accident and impossible without a mouse.
Fixed
Query and Results
- Code editors open — A packaging fault in the 1.8.2 build could stop any code editor from opening — a query tab, Validation Rules, Snippets, the log viewer — from the first time one was needed.
db.orders.find()works, and the editor agrees — The shell's collection shorthand, which is what most people type first, came back as an error that read like a broken app rather than an unsupported spelling; it resolves to the collection now, and the editor stops underlining it in red and offers your collections as completions ondb.with their sampled document types. A collection sharing its name with a driver method (stats,admin,command) still needsdb.collection('stats'), in the editor as at runtime.- A shell-style projection is refused, not ignored —
find({}, { name: 1 })is shell syntax, and the query editor runs the Node driver, where a projection in that position is silently dropped — so the query looked like it had worked and returned every field. It now says so, and names both spellings that do work. - A script runs all of its statements — Two writes pasted one under the other performed only the first, and reported success; every statement runs now, in order, and the query is not reported finished until the operations it started have. A query can also end in a line comment, which used to fail the run.
- A comment can no longer switch off the result cap — Mentioning
.limit(anywhere in the query, a comment or string included, disabled the automatic cap and read the whole collection into memory — the exact thing the cap exists to prevent. What counts now is the limit the query actually applies. - An aggregation that writes is not run a second time — Explain re-runs a query to collect its plan and skips
$outand$mergepipelines, but decided that from the query text, so a pipeline that assembled the stage as it ran was explained anyway and wrote twice. - Editing a document keeps its types — Saving an inline edit could narrow the document's other Long and Decimal128 fields to plain doubles, and a 64-bit
_idlost its last digits — enough that Edit and Delete built a filter matching nothing. Large integers survive the trip intact now, and a value the field's type cannot hold is refused rather than written as something else. - A mistyped cell value is refused instead of quietly changed — Entering something that is not a number into a numeric cell stored a truncated or zeroed value and reported success; each case is refused now, naming the value it could not read, and a number too large for its type is refused rather than wrapping. Clearing a required date is refused too, instead of saving null.
- Edit and delete work on any
_id— On a row whose_idis a Decimal128, a UUID or a compound key, both generated a filter MongoDB rejected the moment it ran; a UUID_idalso reads as a UUID in the delete confirmation rather than as raw base64. - The result grid no longer empties itself a moment after the query finished — A request the window had already sent could answer after the run completed and overwrite the result that had just arrived, so the grid went blank with nothing to explain it.
- "Filter by value" on an empty cell finds the documents missing the field — It generated a filter that matches nulls, so the rows it found were not the rows you clicked from.
- A single-document result behaves like any other — On a
findOne, Expand All silently did nothing and search never highlighted anything even when the term was right there. A result that was one ObjectId, Decimal128, Long or Timestamp was also labelled by the JavaScript type it had been decoded into, so an ObjectId read "String". - Double-clicking a column edge fits the column to what it draws — Widths were measured from a different string than the table renders, in the wrong typeface, with the header measured in capitals it is not drawn in — so an object or array column came out about a fifth of the width it needed.
- A duplicated tab keeps how it was set up, and a moved one keeps its selection — Duplicating lost the tree column width, sticky header, split position and full-screen state; moving a tab to another panel cleared the selected rows and the Shift anchor with them.
- Tree view shortcuts follow the Hotkeys modal — Search, copy, select-all, edit-document and toggle-preview were fixed chords whatever you had rebound them to, so the Hotkeys modal listed five shortcuts the tree ignored.
- Closing a tab stops everything it started, not just its query — A stage preview, an explain or a schema analysis left running when its tab closed kept reading, kept its cursor open on the server and kept its row in the footer with nothing left to cancel it from.
- The editor stops offering timers the query cannot use —
clearTimeout,setIntervalandclearIntervalwere autocompleted and then threwReferenceErrorwhen the query ran. All four timers work now, and any the query leaves running are cleaned up when it ends. - Run is disabled on an empty editor — It stayed enabled and sent an empty query to the server, which is a round trip that can only come back empty; Explain had the check all along.
- Editing a document whose
_idis0,""orfalse— All three are legal identifiers and all three were read as "no_id", so Edit Document opened a statement with a placeholder comment where the filter should be. - Ctrl/Cmd+F no longer opens the browser's find bar — Outside a result table the key fell through to the WebView, whose own find bar searched only the rows on screen, fought the result viewer's highlights, and could not be closed from anywhere in the app.
Aggregation Builder
Auto Preview says how long it waits — Its tooltip promised two seconds against a wait of one and a half.
Opening the builder no longer replaces your query — Switching a tab into the builder rewrote its code with an empty pipeline whenever the code was not already an
aggregate(…)call — which is every tab that opens with afind()— and saved the rewrite, so the query was gone on the way back.The formatted query survives the switch back to the editor — "Switch to editor" formats the pipeline and saves it, but the save lost a race with the mode change, so the stored query reverted to the unformatted text and a reopened tab showed something different from the editor.
Stage previews no longer outlive the pipeline they belong to — A stage's preview is the pipeline run down to that stage, so anything above it is part of the answer, but inserting, reordering, deleting or switching off an upstream stage left the cards below showing the previous pipeline's results, doc count and timing included, with nothing to say they were out of date. Each edit now discards the previews below the point it changed and keeps the ones above.
A broken
$outor$mergestage shows its error — The syntax-error marker shared a slot with the destructive-stage warning and always lost it, so a collapsed stage with a broken body looked fine.
Explain and Charts
- Index suggestions are complete — A suggestion dropped the equality or range you had filtered on whenever the chosen index had already absorbed it, so explaining
find({region: 'eu', amount: {$gt: 10}}).sort({createdAt: 1})offered an index that no longer covered the filter; suggestions are the full ESR index now. A$lookupalso always suggests its join index, a suggestion for another collection is judged by that collection's indexes, and a partial or sparse index no longer suppresses the suggestion you need. - The aggregation pipeline shows its per-stage numbers — Rows and time per stage were read from the wrong place in MongoDB's output, so every stage but the initial cursor showed nothing and the four pipeline insights could never appear. The version card is filled in again too.
- Explain follows the code and the collection you just typed — It stayed greyed out on a new tab until you ran the query once, and editing a query to target another collection kept the previous collection's indexes and suggestions on screen.
- A chart that cannot be drawn offers you the way back — The "Chart rendering failed / Reconfigure" message was unreachable, so a configuration ECharts could not express took the result panel down with it; it appears where it was meant to now, with the toolbar and its Configure button still there.
- The field pickers offer fields worth charting — A field holding an object plotted as a single
[object Object]bar, and a field whose name merely ends in "at" —format,chat,heartbeat— was offered as a Date and produced an empty chart with no obvious way out. - Chart colours follow a light theme again — A custom theme whose background is written as
#fffrendered charts with the dark theme's tooltips and grid lines, and one written asrgb(…)did the reverse.
Connections
- One unreadable saved password no longer hides every connection — A connection whose stored secret could not be decrypted with this machine's key made the whole sidebar list fail, healthy connections and all, and it could be neither deleted nor edited because both read it first. An unreadable secret is now simply a missing one: the list renders, the field is blank, and the connection behaves like any other.
- Cancel really cancels — Cancelling a connection test only reset the dialog while the backend kept dialling for the full timeout, and pressing Test again started a second attempt alongside the first. A hanging attempt now appears in Current operations and can be cancelled, a timed-out test closes its attempt instead of leaving it and its SSH tunnel retrying all session, and a query can be stopped while its connection is being checked.
- Turning a form into a connection string asks first — Switching to "Connection String" with X.509, LDAP, Kerberos or AWS-IAM silently discarded those credentials on save, while the string on screen looked complete.
- Atlas detection reads the host, not the whole connection string — A database named
mongodb.net, that string in a URI parameter or password, or a lookalike domain were all treated as Atlas and offered credential setup they could not use. - Kerberos host canonicalization is what it was set to — Monghoul turned it on with a value the driver does not accept, so the host was canonicalized by CNAME instead of the intended forward-and-reverse lookup — which against a KDC that issues principals off the reverse name is the difference between a handshake that works and one that does not.
- Expanding a connection that does not store its password asks for it — Only the Connect action knew how to ask, so the first thing most people do to such a connection — opening it in the tree — reported "This connection does not store its password" as a plain failure with nothing to click, or, more often, said nothing at all: the row opened onto an empty list, the spinner cleared and no message appeared anywhere. Expanding now opens the same prompt Connect does and loads the databases once you have answered. Every other operation that opens a connection — syncing collections or indexes, collection statistics, schema analysis, monitoring, user management — asks the same way.
- Schema overrides can be set back to Default — Once "Auto Schema Analysis" or "Schema Sample Size" had been changed, every later save kept the old value, so a connection switched to "Off" never analysed a schema again with no way back.
- Cancelling a refresh keeps the details it had cached — Cancelling "Syncing indexes" was ignored: the read ran to the end and blanked every index's size and usage statistics while the row reported "Cancelled".
- Pasting a replica-set connection string into Individual Fields does something —
mongodb://a:27017,b:27017/db?replicaSet=rs0— the case where the replica-set fields matter most — could not be read at all, and the paste was ignored without a word: the fields kept their old values. The first host is filled in now, the rest are named as dropped, and a string that genuinely cannot be read says so. - "Show only these databases" with nothing listed stays that way — Saving it turned the filter into "hide admin, config and local" — the opposite question — and removing all three from the ignore list put them straight back.
- Deleting a connection closes it — Its open socket, its SSH tunnel and the password typed for this session all outlived the connection they belonged to, for the rest of the session.
- Copying a collection to another connection picks a database that exists there — Changing the target connection left the previous one's database selected, creating a database of that name on the target server when the copy ran.
Databases and Collections
- Renaming a database keeps what its collections were set up with — MongoDB has no rename command, so a rename is a copy, and it was copying only the documents and the indexes: a validated collection came out with no validator, a capped one came out uncapped, a view was flattened into an ordinary collection, and collation, TTL and time-series settings went the same way. All of it is carried across now, index options included, a database containing a view can be renamed at all, and collections stream in batches rather than being read into memory whole.
- Write protection covers the actions in the connection tree — It only ever covered queries, so a write-protected connection dropped a database, dropped or cleared a collection, renamed either, or rebuilt an index from its own context menus with the same one-click confirmation as an unprotected one — while the identical
dropDatabase()typed into a query tab was refused. Those actions are protected now, and the backend refuses one that arrives without the confirmation, so a second window cannot go around the dialog. - An index list refreshes when the indexes change — Creating, dropping or rebuilding an index re-read the indexes and stored them, then told nobody, so the list kept its old rows — in the window that made the change as much as in any other. A sync run from a second window, or by an MCP client, left every other copy of the tree stale the same way, and Rebuild now keeps every option the index had.
- A database containing a view can be duplicated — Duplicate failed outright with
cannot write to <db>.system.views, the same way Rename used to; views are now recreated as views on the copy rather than flattened into ordinary collections, and a database export stops writing asystem.viewsfile no import can use. - Validation rules save and clear properly — Clearing the validator and saving closed the modal as if the rules were gone while leaving them on the collection, "Insert $jsonSchema Template" produced a validator MongoDB refuses outright, and Remove Validation from the sidebar left the rules dialog still showing them.
Import, Export and Background Tasks
- A collection copy keeps the validator, the cap and the collation — Copying a collection, or duplicating a database, created each target implicitly by inserting into it, so it arrived with server defaults: a copy of a validated collection accepted the writes the original refused, and a copy of a capped one grew without bound. Neither showed up until long after the copy. The same fix means an empty collection now reaches the target at all, where a document copy alone never produced one.
- A copy says how many documents the target refused — "Insert — append all documents" over a target that already holds some of those
_ids skips them, and the count was calculated and then thrown away, so a copy that landed half its rows read as a plain success. - Exporting all matching documents asks first, and really ignores every limit — The scope re-runs your query without your
.limit(),.skip()or the loaded-result cap, so it can read an unbounded number of documents; that was a tooltip, and only a query that looked like it might modify data was confirmed. One dialog now covers both, before the save location is chosen. It also drops a limit written asfind({}, { limit: 10 }), which used to survive and write exactly ten documents. - A CSV or Excel export keeps the fields that only some documents have — Both took their columns from the very first document, so a later document's extra field never appeared in the file — no warning, and a document count that still said everything was exported. Columns now come from a sample of the export, and a field that turns up after that sample is reported rather than dropped.
- A document with
typeandvaluefields exports whole — Exporting a query result mistook any document carrying both of those names for an internal wrapper and wrote out itsvaluealone, dropping every other field including_id. - An empty object or array keeps its column — A field holding
{}or[]contributed no column at all to a CSV or Excel export, so an empty field was indistinguishable from an absent one. Dates are no longer flattened away either: a date field, at any depth, exports as its ISO string. - A headerless CSV imports into named fields — With the header checkbox off, every document arrived with its fields called
"0","1","2"— the column indices, as strings. They arefield1,field2,field3now. The Auto-convert Types checkbox also stops promising ObjectIds, which it never produced; what it does convert is numbers, booleans, nulls, ISO dates and inline JSON. - The same file imports the same way, one collection or a whole database — The two import paths carried separate copies of their type inference and had drifted: importing one collection read
1e5and0x10as numbers, left an inline{"a":1}cell as text, turned any 24-hex string into an ObjectId, and read any Excel number between 25569 and 50000 as a date — so a quantity of 30000 was stored as a day in 2052. All of it silent, and none of it what the Auto-convert checkbox describes. Both paths now run one implementation, and a row with a stray extra cell no longer fails a whole database import. - A CSV column of identifiers stays text — With type inference on, values like
order-1,sku-2andQ1-2024were read as years and imported as dates; a real date column is still detected. - Cancelling a task actually stops it, and is recorded as stopped — Cancel only muted the progress row: the export, import, copy or generation ran to the end, so a "cancelled" export still read everything and left the complete file on disk. The work is terminated now, a cancelled export removes its incomplete file, and the operation log records the cancellation and how much made it across rather than filing a success.
- A drop-then-insert import asks before it drops a write-protected collection — It was the one destructive path in the app that went around write protection entirely: no confirmation, no refusal from the backend, just a red note in the form shown whether or not the target was protected.
- A failed export no longer deletes the file it was going to overwrite — When an export failed before it had written anything, the cleanup removed the target anyway — a file the user had picked in the overwrite dialog and that this export never touched.
- A background task no longer takes the backend down with it — Each of these writes a log entry and records telemetry when it finishes, and a failure in that bookkeeping could stop the local server, so the app went unresponsive — with its window still open — over a task that had actually completed.
- A failed or silent task reports itself — A failed database export or duplication reported nothing at all: the bar emptied, the row kept saying it was running, and it vanished when the row expired. A task whose worker exited without reporting left its row at "running" for the rest of the session, with the modal waiting on it never closing.
- A finished task stops offering Cancel — A completed export lingers ten seconds so you can see its verdict and kept a Cancel button the whole time, which rewrote the task as "Cancelled by user" and contradicted the file on disk. Finished rows show a Done badge, the footer counts only what is running, and a task that finishes in under half a second no longer leaves its progress dialog on a stuck bar.
- Generating a schema from the editor shows up in the footer — The "autocomplete is limited" banner runs a real sampling analysis per collection, and it ran invisibly: no progress and no way to stop it. Each collection is a footer operation now, named after the collection and cancellable.
Operation Logs
- The free tier's 250-entry limit is the limit it advertises — The cap was applied to the page size the modal asks for, which is 50, so it never once bit: a free user paged back through every stored entry beneath a label reading "Free: max 250 entries". How far the list reads is what changed — nothing is deleted, and your own retention setting still decides what is kept.
- Log trimming keeps the newest entries — Timestamps are stored to the second, so when the retention limit hit a burst logged inside one second — a script in a loop, an import logging per batch — which entries survived was arbitrary.
- The retention setting refuses values that would break logging — A fractional limit set through the API stopped every later entry from being written until it was changed back, and 0 was accepted and then behaved as 500; both are rejected now, pointing at Unlimited (-1). A value the backend refuses also stops looking as though it was saved.
- The log list stops blanking itself while you read it — Under a relative filter such as "Last Hour" the window boundary moved every thirty seconds, which threw away the loaded page and showed "Loading logs..." until the next round trip.
- Selecting a log works — In multi-select mode the tick box did nothing at all, so selecting only worked by clicking the rest of the row; Escape now deselects properly too, instead of clearing the selection and immediately re-selecting the first row.
- An assistant's data moves are recorded — The MCP
clone_databasetool wrote no entry at all, on success, cancel or failure, while every other data-moving operation logged every run — so the largest copy an assistant can start was the one that left no trace.
Users, Roles and Monitoring
- A user-management tab that cannot load says so — It painted an empty users table, as if the connection had no users, and Refresh retried everything except the read that had failed, so the tab had to be closed and reopened.
- A custom role on Atlas only offers what Atlas can grant — The action grid listed every self-hosted MongoDB action and the resource picker offered "Any resource", neither of which Atlas supports, so picking one looked fine until it came back as a generic API error on save.
- A role scoped to one database is refused before it reaches Atlas — Creating or editing a role with a cluster-wide privilege, a privilege on another database, or an admin-only inherited role gets the same actionable message on an Atlas connection that it always got on a self-hosted one; it used to reach the Atlas API and come back as a generic error.
- Create Role stops offering Atlas a command Atlas rejects — The form shows the equivalent
mongoshcommand, and on an Atlas-managed connection it showeddb.createRole(...), which Atlas does not permit — a command you could copy, run and be refused by. The user form already declined to make that kind of claim; the role form does now too. - Deleting the last user-administrator asks even when the check cannot run — On an account that cannot list users, the safeguard was silently absent: indistinguishable from a clean "this is not the last administrator". It now says it could not check and asks you to confirm, as it does when it can check and the answer is yes.
- A self-hosted connection is no longer mistaken for Atlas — Detection scanned the whole connection string for
mongodb.net, so a database namedmongodb.net, anappName=mongodb.netparameter, or a lookalike host forced the connection into Atlas's read-only mode with user management greyed out and no way to reach it. - The health panel says when the lock queue is clear — The row only appeared once something was queued, so an idle server showed nothing rather than a green row; Active Ops also gained a critical level, having previously only been able to warn.
- Cluster Monitor stops answering after a downgrade — Only opening the tab was checked against the licence, so a monitor tab already in the workspace kept polling the server, killing operations and setting profiling levels on the free tier.
- A missing role is reported as one — A permissions rejection in the Operations and Profiler tables sometimes showed the generic error panel instead of the "requires the clusterMonitor role" explanation.
Workspace and Sidebar
- A tab, split or panel close no longer undoes itself a moment later — The layout is re-read from the backend at a few ordinary moments, and a change that landed while one of those reads was in flight was put back: a tab you had just opened vanished, a split you had just made collapsed, a panel you had just closed returned.
- A tab move or split can no longer wedge the workspace — With two windows open, moving or splitting into a panel the other had just closed left the workspace pointing at a row that no longer existed, after which every new tab failed outright.
- Closing a detached window no longer destroys its tab — Every other way of closing a tab can be undone with Restore Closed Tab; the floating window's own close button deleted it outright.
- A detached window reopens at the size you left it — On a high-DPI display it came back scaled by the display's factor, so on a typical 2× laptop screen it was twice as wide and twice as tall and pushed the same distance down and right, often mostly off-screen.
- A tab closes the moment you close it, however you closed it — Middle-clicking, "Close Tab" and "Close Other Tabs" all waited for the server first while the ✕ and the keyboard shortcut did not, so on a slow connection they read as a click that had not registered. Closing a panel also leaves one of its tabs selected, rather than showing a tab strip over a blank body.
- Closing a tab stops its query — A tab closed mid-run left the query going: it held its cursor open on the server for as long as it took, kept a row in Current operations named after a tab that no longer existed, and wrote its result back into the closed tab when it finished. The only way to stop it was to find it in the footer. Closing the tab cancels it now, however you close it — the ✕, the shortcut, Close Other Tabs, or deleting the connection underneath it. Closing a progress dialog still does not stop anything: an export or an import outlives the window you started it from, by design, and is cancelled from Current operations.
- Revealing a collection from anywhere reports a failure, and asks for a missing password — Opening a collection from the command palette, a deep link or an assistant ran the same sync that expanding the row in the tree runs, but with no error handling: a failed sync said nothing at all and left a row that had visibly opened onto nothing, and a connection that does not store its password failed silently where clicking the row prompted for it correctly. Both paths run one implementation now.
- A folder can no longer be dropped inside itself — The sidebar refused it but nothing behind it did, so a second window or an assistant could still make the move — after which the folder and everything inside it disappeared from the sidebar, still holding your favourites but unreachable, with the next drag freezing the window.
- Removing a favourite no longer opens it — The ✕ on a favourite and the pin on a pinned collection sat inside the row's own click target, so one click did two things: the action you asked for, and then opening the item you had just removed.
- Opening a favourite from the command palette does what opening it from the sidebar does — Three differences, all of them the palette's: a markdown favourite opened as a query tab running the note's own prose as code; selecting a favourite that already had a tab opened a second copy instead of switching to it; and every saved view setting — table layout, pinned columns, read preference, page size, chart — was dropped, so a favourite saved as a chart came back as a table. Both paths run one implementation now.
- A favourite saved from the tree view comes back as one — The key-column width and expansion state were recorded on every query favourite and read back by nothing, so a favourite saved from the tree view reopened with them reset.
- Ctrl/Cmd+S is no longer swallowed on tabs that cannot format — The shortcut is Format Query, and it was claimed on every tab type, so on a note or the theme editor it consumed the key and did nothing — which is why the theme editor's own save only worked when the cursor happened to be in a text field.
- Favourites and pins reach your other windows — Only deleting a favourite was announced, so a rename, a reorder or a newly saved one left every other window showing the old list until something unrelated refreshed it.
Themes and Snippets
- Markdown code blocks follow your theme — A single dark syntax palette was loaded for every theme, so a code block in a markdown file rendered dark-on-light under all four light themes and ignored a custom theme entirely. Code now uses the same colours the result tree and table already use for the same kinds of value.
- A chart tooltip is drawn in your theme's colours — It picked one of two hardcoded backgrounds by dark-or-light verdict, so every light theme got the identical white tooltip and every dark one the identical near-black; the gaps between pie slices had the same problem.
- Cluster Monitor's row tints follow your theme — The red, amber and blue washes marking a collection scan, a slow operation and a newly started one were fixed colours that ignored the theme they were drawn on.
- The theme menu still lists the built-in themes when the theme list cannot be read — A failed read published an empty list, so the footer's theme button opened over a menu with nothing in it: no themes, no message, no way to try again, and "Customize Themes…" gone with them.
- A newly installed font shows up without a restart — The system font list was read once for the life of the app, and an answer with no fonts in it used to be cached and leave the pickers offering nothing at all for the rest of the session.
- Revert after saving a copy of a built-in theme goes back to the copy — It restored the built-in, so the editor lost the custom theme you had just saved and the next Save created a second copy of it.
- A theme that fails to apply falls back once — A failure the theme itself did not cause could end with no colours written at all.
- A snippet write that fails says so — Adding, deleting, reordering, restoring the defaults and the autosave all swallowed their errors, so the modal looked like it had worked — which with autosave meant carrying on typing in the belief the work was saved.
- A snippet saved as the default becomes the default — The old default stayed flagged as well, so the app kept using it and the new one was silently ignored, and neither could be deleted afterwards because each looked like the protected default.
MCP Server
- Limit Access covers the task list — An assistant could see every operation the app had in flight regardless of the allowlist, collection names included, and could cancel or inspect one on a connection it was not allowed to touch.
- A restricted allowlist no longer makes history and tab search look empty — Both fetched a page of results and applied the allowlist afterwards, so a page whose rows were all out of scope came back empty while plenty of matching rows sat further down — and an assistant reads an empty page as "there is nothing there".
- The allowlist forgets databases that no longer exist — A ticked database kept its name after being dropped or renamed, matching nothing, until a database with that name was created again — at which point it was reachable without anyone having ticked it.
- An assistant is told when nothing was there — Deleting a connection, or renaming or removing a folder, that did not exist answered "Connection deleted" / "Folder renamed", which reads exactly like having changed the one that was meant.
- Removing a favourite through the assistant unlinks the tabs that used it — The favourite went and every tab still pointed at it, so the toolbar showed the tab as favourited and its "update favourite" button wrote to something that no longer existed.
Data Generation
- Generated documents keep the types the collection stores — An array of numbers, dates or booleans was filled with random words, and a sometimes-null field was treated as text, so a generated document did not resemble the collection it was modelled on and any index or validator over those fields rejected it.
- A collection with a unique index can be generated into — Values for a field under a unique index were drawn at random from a few hundred words, so a run of more than a handful of documents died on a duplicate-key error and inserted nothing.
- Places generate as places — A GeoJSON
coordinatesfield was filled with a random number of words, so every collection carrying a 2dsphere index refused the entire batch; points generate as a real coordinate pair now, and line strings as two of them.
Elsewhere
- A setting that could not be saved says so, and puts itself back — The MCP settings, every preference in the App Menu drawer, every theme save, duplicate and delete, and all five sidebar folder writes were silent about failing, so the control kept showing what you had chosen while the app had stored something else, until a restart quietly undid it.
- Copy buttons tell you when the copy failed — Every "Copied" toast fired without waiting for the write, so a denied clipboard permission still reported success — the licence key, the MCP connection details, a generated schema, the builder's code and the data-generator preview included.
- One error message instead of two — The command palette, the sidebar's context menus, the export and import modals' file-location field, and opening Cluster Monitor or Users & Roles each reported a failure twice, sometimes in two different wordings, and on the free tier dropped an error toast on top of the upgrade prompt that had just opened.
- A subscription that comes back restores Pro — A customer whose payment failed and who passed the grace period stayed on free even after fixing it: the app asked the licence server on every start, was told the subscription was active, and did nothing with the answer.
- One update checker instead of two — The startup check and the App Menu's "Check for Updates" button each kept their own copy of everything, so pressing the button in the first few seconds ran two checks at once, installing from one left the other still offering the same version, and a lifetime plan past its update window was called "expired" in one place and "check failed" in the other.
- The connection limit is enforced wherever you add one — On the free tier, the keyboard shortcut and the command palette walked past the limit the sidebar's own button applies, so the attempt failed at save time instead of offering the upgrade.
- Recording a shortcut onto Cmd/Ctrl+1–9 is refused, not silently ignored — Those nine belong to "Go to tab 1–9", which cannot be rebound, so a shortcut recorded onto one was accepted, saved, and then never fired.
- The local server is reachable only from this machine — Both the backend and the MCP server listened on every network interface with the access token as the only gate; the MCP port is one you choose and keep, so on an untrusted network it was visible to anyone on it. Both are now bound to loopback.
- Ctrl/Cmd+E and Ctrl/Cmd+P stay out of the way while you type a note — They switched a markdown note between editing and preview mid-word, and stopped working entirely under CapsLock or on a non-Latin keyboard layout.
- Switching tabs quickly no longer writes errors to the log — Opening or switching a tab before the previous one's data arrived cancelled that request, which is routine, but the cancellation was recorded as an uncaught error, so an ordinary session filled the log file — and any diagnostics bundle sent with it — with failures that never happened.
Downloads for v1.9.0
Installers published with this release. The current version is v1.11.0.