Skip to content

Fix/working state - #47

Open
k05j wants to merge 33 commits into
developfrom
fix/working_state
Open

k05j wants to merge 33 commits into
developfrom
fix/working_state

Conversation

@k05j

@k05j k05j commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Closes #42

k05j added 22 commits November 3, 2026 10:22
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.
@k05j
k05j requested a review from DerCookieLiker November 3, 2026 10:54
k05j added 7 commits November 3, 2026 11:54
…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.
k05j added 4 commits November 3, 2026 12:12
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ctrl+Enter runs the query in every open tab

1 participant