Repository navigation
Conversation
the documentation of 2026-11-03 found bugs in nearly every feature: the build fails, the driver pool never lets go of a client, tab views multiply and run queries several times, query errors are swallowed, shell functions silently match nothing. the plan fixes them and completes what querying needs (paging, count, sort, projection), lets several connections be open at once and accepts connection strings with tls, replica set and timeout options. decisions 1 to 4 are the answers given before the plan was written, 5 to 23 were agreed with the go-ahead to build. thirteen steps, one commit each with its tests.
vue-tsc type-checks the tests with noUnusedLocals, so the unused import of T in tokens.test.ts stopped pnpm build and with it pnpm tauri build. the test called "throws" parsed a trailing comma without asserting anything; it now says what it checks.
node_modules was installed with pnpm 10 while the machines run newer versions, so every pnpm command wanted a fresh install from another store. pnpm is now pinned in packageManager (corepack) and devEngines.packageManager (pnpm itself, which switches to the version and records it in the lockfile), the same way as in smtp-relay. pnpm 12 refuses to install while build scripts are not approved; pnpm-workspace.yaml allows the two this project has: esbuild and vue-demi.
every package to its latest release, with three exceptions decided with the update: typescript stays on 6.0 because vue-tsc can't run on typescript 7 (it no longer ships lib/tsc); zod and vee-validate follow in their own commit, because @vee-validate/zod only supports zod 3. lucide-vue-next is deprecated and replaced by @lucide/vue, same icon names. typescript 6 deprecates baseUrl; paths resolve relative to the tsconfig without it. tanstack table 9 only exposes the apis of registered features, so the document table now declares them (sorting, ordering, visibility, sizing, resizing, the sorted row model and the sort functions 'auto' picks from) and uses useTable, sortFn and the FlexRender shorthand. tests for the table check columns, sorting, the saved column order and replaced data against the new api; vitest now runs in happy-dom for them. vue-tsc 3 misread the '//' in the host field's placeholder "localhost or mongodb+srv://..." and reported errors further down the template. the hint was wrong anyway, srv strings don't work yet. checked: tests, pnpm build, the start screen renders in the browser.
claude code starts the vite dev server on :1420 through it to look at the ui in a browser pane. outside tauri the ipc calls fail, but layout, styles and icons render. same file as in smtp-relay.
zod 4 is out, but @vee-validate/zod, the bridge between vee-validate and zod, only supports zod 3 and hasn't been released since june 2025. vee-validate 5 would take zod 4 directly, but it has been a beta since august 2025. tanstack form takes zod 4 as it is (standard schema), is released regularly, and is what shadcn-vue documents next to vee-validate. the connection dialog keeps its behaviour: the same fields, required name and host, port 1 to 65535, test and save validate the same schema (onSubmitMeta says which of the two runs), the password stays loaded in edit mode, closing resets the form. the dialog no longer logs its form values at trace level, they contained the password. vee-validate, @vee-validate/zod and the vee-validate based shadcn form components are gone; shadcn-vue's field components replace them, taken from its new-york-v4 registry with the imports the cli would write. checked in the browser: a port of 99999 shows the error under the field, valid values reach the store on create and on test.
every crate to its latest stable release: tauri 2.12 with its plugins (matching @tauri-apps/api 2.12 on the npm side; tauri 3 is an alpha), mongodb 3.9, tokio, uuid, thiserror, log, futures. bson stays 2.15, which mongodb 3.x still uses by default. keyring 4 moved its credential stores into crates of their own; its default v1 mode keeps the api of keyring 3 and uses keychain, the windows credential manager and the secret service. on linux that drops the keyutils cache and the vendored openssl of keyring 3. passwords saved with keyring 3 stay readable: a check program read the three stored passwords on the development machine with both versions. the build needed tauri's linux packages, which were not installed on the development machine: the earlier cargo check only reused build results from march. the docs said it passed; they now say what was checked and name the packages for ubuntu 26.04, including libdbus, which tao links since tauri 2.12 and keyring 3's vendored feature used to build from source. checked: cargo check without warnings. cargo clippy reports 15 warnings, all in code that predates the update (it could not compile before).
the parser turns shell functions into extended json, and bson in the
rust core reads those shapes as bson types. what bson can't read stays
a plain object, and the query matched nothing without an error:
ISODate("2024-01-01") (bson wants a full rfc 3339 date), an ObjectId
that isn't 24 hex characters, BinData with its subtype as a number.
now dates are normalised to utc rfc 3339 (a date or date-time without
zone is utc, as in the shell), and every function checks its arguments
and throws a parse error the query tab shows. RegExp and /re/flags
literals become $regularExpression, which also works inside $in.
added: NumberInt, UUID, new before a function, dotted keys without
quotes, numbers with exponents. a missing comma, an unterminated string
or regex, and anything after the query are errors now instead of being
skipped.
checked: 70 tests; a scratch program with bson 2.15 turns every shape
the parser produces into the expected bson type.
CommandRegistry.unsubscribe used findIndex(subscriber), which calls the subscriber as a predicate. for the query view that ran a query, and because the returned promise is truthy it removed the first subscriber, whichever it was. it uses indexOf now. shortcuts are bound to "mod", which is cmd on macos and ctrl elsewhere, so mod+w, mod+shift+d and mod+enter. tauri's default macos menu binds cmd+w to "close window"; on macos the app now builds that menu without it (src-tauri/src/menu.rs), keeping the edit menu that copy and paste need there. the menu is compiled on every platform but only used on macos, and can't be checked without a mac. tests run with tauri's ipc mocked (src/test/setup.ts), so code that logs can be tested. new tests for the key strings, the registry and the bindings.
the repository reached the keyring through a concrete CredentialService, and every command that needed a password downcast the repository to FileRepository to get at it; another repository type would have failed with "unsupported repository type". passwords now go through a CredentialStore trait, the repository offers get_password and hydrate, and the commands use those. hydrate_connection_with_credentials and the as_any downcasts are gone. the keyring store reports a missing entry as CredentialError::NotFound, so "no password stored" and "keyring broken" can be told apart. update now keeps the id of the path, not one sent in the body. the repository has its first tests: save, get, update with and without a new password, switching to no authentication, delete, a failing keyring, empty, corrupt and foreign-version files. they run on a temp directory with an in-memory credential store, never on the real keyring. 21 tests.
a connection was a host in a field called uri, a port and a connectiontype nothing read; the driver built mongodb://host:port by hand, so srv strings and every option of a connection string were out of reach, although the host field suggested mongodb+srv://. a connection now has a target, either host and port or a mongodb:// or mongodb+srv:// string, and options for host targets: tls, ca file, invalid certificates, replica set, server selection timeout. connectionType is gone. before a connection is stored it is checked, and credentials in a connection string move out: the username into the authentication, the password into the keyring; the stored string has no user info. a refusal says why, and the dialog shows that text. the driver gets ClientOptions instead of a string: the builder for a host, ClientOptions::parse for a connection string. the driver checks some options against the user info (an authMechanism needs a username), so the stored credentials go back into the string, encoded, right before it is parsed. the authentication database is the form's, else the string's authSource, else admin. the url crate is gone. connections.json becomes version 2. a version 1 file is migrated on the first load and kept next to it as connections.v1.json; a v1 "uri" that is a connection string becomes a string target with its password moved to the keyring. the ui sends host targets; fields for connection strings and the options follow with the dialog. tests: uri splitting, normalising, the migration, the repository with version 1 files, the client options of both modes. integration tests, skipped by default, connect to a real mongodb by host and by string and check that a wrong password is an authentication error; they passed against a mongo:8 container.
the rust core kept one driver per connection for the whole process and
nothing ever removed it. a driver is created before the server is
contacted, so after a connect with a wrong password, or after editing
host, port or password of a connection used in this session, the old
client stayed until the app restarted. disconnect in the ui only reset
the ui.
commands that talk to mongodb now take a connection id and the rust
core loads the saved connection and its password itself, so the ui's
copy can't be stale and the password no longer passes through the web
view for them. a driver leaves the pool when its first command fails,
on the new disconnect command, and when its connection is edited or
deleted; the next command builds it from the saved settings.
list_collections now loads the password like the other commands, and
the sidebar shows its errors. a keyring that can't be read is
CredentialsUnavailable ("edit the connection and enter it again")
instead of "verify your host"; a deleted connection is
ConnectionNotFound. release builds log at info, development builds at
debug.
removed: the synchronous test_connection and get_databases commands,
get_connection, and the ui's ConnectionRepositoryIPC with its
save_connection(s) wrappers for commands that no longer existed.
tests: the pool with a fake driver (reuse, disconnect, a failing first
command, unknown connection, missing password), the driver's error
messages, the ipc calls of the connection service. 75 rust and 93
frontend tests.
a user who may only read one database failed the test at "reading
status", although connecting and reading would have worked: serverStatus
needs a privilege like clusterMonitor. that stage is now skipped when
mongodb answers unauthorized (code 13), and the test goes on. the
version comes from buildInfo, which needs no privilege, and is shown
under its stage ("MongoDB 8.0.x").
a failing stage was never marked failed: the error returned before its
status was reported, so the stepper stopped at a grey stage and only
the toast said why. stages now have the states pending, passed, failed
and skipped, and a failing stage is reported before the error returns.
the titles existed twice, in the driver and in the store. the driver
now reports every stage as pending first and the store builds its list
from the events. the events carry a test id chosen by the ui; before,
the id was empty and a listener couldn't tell two tests apart.
tests: integration tests for root (every stage passes, version named),
a user with read on one database (status skipped) and a wrong password
(ping failed); store tests with tauri's event mock for the events of
its own test, failures and cancelling.
start_find_documents takes one FindQuery: database, collection, filter,
sort, projection, skip and limit (1 to 10000, a limit of 0 would mean
no limit to mongodb). filter, sort and projection must be objects; a
number or an array is an error that says which part is wrong.
the count runs next to the find. an empty filter is counted with
estimatedDocumentCount from the collection's metadata instead of
scanning the collection, and the result says it is an estimate;
a filter is counted exactly.
a failed find dropped the driver's message and only said "check your
permissions". FindDocumentsFailed now carries the server's message
("unknown top level operator: $foo"), and missing read rights say so.
documents leave rust as relaxed extended json: dates as iso strings,
which the table and the json view read. an int64 beyond ±2^53 stays
{ "$numberLong": "…" } at any depth, because a javascript number would
change its value.
tests: the json/bson conversion both ways; integration tests for sort,
projection, paging, both counts, a broken query and reading without
permission, passing against a mongo:8 container. the ui sends the query
object; sort, projection and paging get their inputs with the query tab.
the dialog connects by host and port or by connection string. host mode has collapsible options: tls with a ca file and "allow invalid certificates", replica set, server selection timeout. a connection string carries its own options; the field says that credentials in it are moved out on save, the password to the system keyring. editing a connection opens it in its mode, with the options open when it has any. the form's values, zod schema and conversion to a connection live in connectionForm.ts and are tested without mounting the dialog. instead of disabling test and save until every field is filled, both validate and show what is missing under the field. deleting a connection asks with a dialog of the app (ConfirmDialog with useConfirm) instead of the browser's confirm(). checked in the browser: options, mode switch, the error for a string without mongodb:// scheme.
the store kept one active connection, and its tabs outlived it: after
switching to another server, the old tabs came back and queried the
new one with their database and collection names.
connections now open side by side. the store keeps a session per open
connection (connecting or connected, its databases, its collections
cached until refresh); the grid gives way once one is connected, not
while it connects. the sidebar shows one node per open connection with
refresh, edit and disconnect, or cancel while it connects; editing an
open connection reconnects it with the new settings. the header's
dropdown opens further connections and marks the open ones.
a tab belongs to its connection ({ connectionId, database, collection })
and shows connection and database under the collection name. closing a
connection closes its tabs; mod+shift+d closes the active tab's
connection.
every tab container rendered the view keyed by the active tab, so each
container made its own copy of every tab that became active, and each
copy ran its query: with n tabs, opening one ran up to n queries, and
ctrl+enter ran in all of them (issue #42). every tab now has exactly one
view, given its tab id, and mod+enter only runs in the active tab.
closing the last tab left its id as the active tab; now there is none.
tests: the store with two connections, failed and cancelled connects,
disconnect closing only its tabs, delete of an open connection, the
collection cache, an edit of an open connection; the tabs store.
checked in the browser with a stand-in for the ipc: two connections,
one query per opened tab, ctrl+enter only in the active tab,
disconnect closing only its tab.
the query tab had a filter and a limit. skip was always 0, the total was counted and dropped, a failed query showed nothing and left the old results with an unhandled rejection, and a running query couldn't be stopped. issue #12 had listed paging, sort and projection. now the tab has sort and projection inputs next to the filter (the same shell syntax and autocomplete), shows "51–100 of 1,234" ("~" for the estimate of an unfiltered collection) with previous and next, and pages through the query that was run, not what is typed since. executing, mod+enter and a new limit start at the first page. while a query runs, execute turns into cancel. a parse error names the input, a server error shows the server's message, and the old results stay. the query goes through ConnectionServiceIPC.findDocuments like the rest instead of calling invoke. the autocomplete knows the fields of every document, not only the first one's (new Set(...arrays) only read its first argument). the logic is in components/query/queryState.ts with tests. checked in the browser with a stand-in for the ipc: paging to the last page, sort and projection sent and reset to page one, both kinds of error; the controls fit the default window of 1200 px.
the connection list was loaded by App.vue and again by the grid on every mount, and after saving by the grid and the dropdown. it is loaded once at start; create adds what the rust core returns. after an edit the store kept the form's data instead of what the rust core stored, so a connection string could stay in memory with its credentials and show in the dropdown. the store now reloads the list from the rust core after an edit. the json view's watcher began with if (!editors.keys()) return, which never returns: an iterator is never falsy. left over from the vite template: the vite favicon, the title "monolens", and vite.svg, tauri.svg and vue.svg, which nothing used.
every feature file describes the state after the round: how it works now, the decisions with their commits, the building steps of 2026-11-03, and only the limits that are still open. the overview has the new command table, the driver pool, the sessions of several connections, how a query travels with sort, projection and paging, connections.json version 2, and what was checked and what was not. the decision log has the round's decisions in every area, the readme lists the features as they are. the plan has its status note (commits, the package update that came in between, the changed step order, what was added and what could not be verified) and moved to docs/plans/implemented. claude.md lost the gotchas that are fixed and says how to check the ui in a browser with a stand-in for the ipc.
the review before pushing found these, all fixed with tests: - the dialog prefilled the auth database with "admin", which beats the authSource of a connection string, so a string with authSource=shop authenticated against admin. the field is empty by default now; empty means admin for a host and the string's authSource for a string. - the connection test used the form data as it was. credentials typed into a connection string were dropped without "use authentication", or written into the string a second time with it. the test now checks and normalizes the connection like saving does (InvalidConnection). - opening the edit dialog a second time for the same connection showed an empty form: it only filled on a change of the connection. it fills on every opening now. - "db.local:27018" in the host field worked with the old url building but not with ClientOptions. it is refused with a hint to the port field, and the version 1 migration takes the port off such a host. - the pool stayed locked while a driver loaded its password and connected, and every command locked its driver for the whole query, so one slow srv lookup or query held up everything. drivers are shared without a lock now (the mongodb client is thread-safe), and the pool is only locked to look up and insert. - a new limit took effect although the query didn't parse, so paging and the label no longer matched the shown result. - the field names of the table and of the autocomplete were computed by two copies of the same code; both use utils/documentFields.ts. - a date with a space instead of T was read as local time.
the docs describe the working state now, so their extraction was redone; the bug nodes of fixed bugs left the graph.
…age waits a connection with a wrong port waited the driver's 30 s server selection timeout at "ping database" without any sign of progress, which looked like a hang. the test now uses 10 s when neither the option field nor the connection string sets a timeout; connecting keeps the driver's default. the running stage shows its seconds from the second second on.
the six docs changed since the last extraction were read again, including the three whose cache missed the review fixes.
vite 8 warns that __dirname won't work once its native config loader becomes the default. the ts-expect-error on process was stale: vite's types bring in node's, so process is typed.
the app was only ever built on linux. the workflow runs the frontend tests, the build, cargo test and cargo clippy on all three platforms for pushes to develop and main and for pull requests, so macos and windows are at least compiled and tested. the integration tests need a mongodb and stay local.
the review found that the windows runner would check out crlf, and that cancel-in-progress also dropped the run of a merge commit when another followed quickly.
the #[ignore] tests only ran when someone remembered to start a mongodb locally, so a driver change could break real queries with ci green. a linux job now starts mongo:8 as a service container, creates the reader user and shop.users the tests expect, and runs cargo test -- --ignored. service containers exist only on linux runners.
mongo:8 is mongodb 8.3 now, which refuses to start on linux 6.19 and newer (SERVER-121912), and the github runners have such a kernel, so the service container failed. 8.0 refuses as well; 8.2 starts, here on linux 7.0 too, and all 12 integration tests pass against it.
ci.yml, CLAUDE.md, the decision log and the app shell doc were read again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #42