Conversation
Capture the design from the grilling session for a Rust Memgraph console: - CONTEXT.md glossary (Core, Frontend, Session, Value, Record, Import mode, cypherl, vertices-first ordering) - ADR 0001: pure-Rust Bolt (bolt-client/bolt-proto) over mgclient FFI - ADR 0002: async Core with a Core/Frontend seam, streaming-first records - PRD with 45 user stories and testing via live memgraph/memgraph containers - 32 tracer-bullet issues (dependency-ordered, TDD-sized) + README landing page
Throwaway testcontainers spike (memgraph:3.10.1, Bolt v4.4): all 21 Memgraph Value types decode faithfully through bolt-client 0.11 + bolt-proto 0.12, zero codec extensions. ADR 0001 confirmed; mgclient FFI fallback not triggered. Grilling outcomes folded in: - ADR 0003: Core-owned Value model, enum normalised at the Bolt boundary - ADR 0004: owned RecordStream + QueryResult shape (summary after drain) - CONTEXT: add Enum, Query result; rename Result stream -> Record stream - Sharpen issues 02/06/07/13/14/32 (incl. TransientError is not a reliable retryable signal) - Local testing only, no CI for now 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Two-crate workspace: mgconsole-core (lib) + mgconsole (binary).
- Session connects by host/port over pure-Rust Bolt (v4.4..v4.1), runs a
query, returns QueryResult { header, RecordStream, summary } (ADR 0004).
- bolt_proto::Value -> core Value translated at the boundary; Value is
total (every spike-confirmed type) and the enum sentinel map is
normalised to Value::Enum here (ADR 0003).
- RecordStream is owned (no futures::Stream in the public API); next/collect
are async so slice 21 lazy-pull is a drop-in.
- CLI pipes a query from stdin, drives the async core via block_on, prints
tab-separated tabular cells.
- testcontainers harness (tests/common) + RETURN 1 integration test; local
cargo test, Docker daemon only. No OpenSSL/C linkage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Float rendering keeps a decimal point (3.0 not 3); NaN/Inf handled. - String rendering escapes control whitespace (\\ \n \r \t) so cells stay on one line; quotes left literal (CSV quoting is slice 22). - Reusable golden harness (tests/golden/check_tabular) comparing rendered cases to tests/golden/tabular/<category>.txt; UPDATE_GOLDEN=1 regenerates. Slices 04-07 add categories. - Tabular conventions documented in the issue (mgconsole goldens absent here). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Thread a `quote` flag through rendering: a top-level string cell is unquoted, but strings nested in a list/map are quoted with full escaping (mgconsole/Cypher convention). - Lists/maps render with elements/pairs, empty forms, arbitrary nesting; map keys unquoted and sorted (BTreeMap) for deterministic goldens. - New `containers` golden category. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Node -> (:Label1:Label2 {props}); collapses cleanly with no labels/props.
- Relationship/UnboundRelationship -> [:TYPE {props}] (standalone form,
no endpoints).
- Path reconstructed as (n)-[r]->(n)... from Bolt's node/rel/sequence,
honouring per-hop direction (signed relationship index).
- Property values reuse the nested (quoted-string) rendering from 03-04.
- New `graph` golden category.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Match Memgraph's textual conventions (captured via toString against a live
3.10.1 container):
- 6-digit microsecond precision on all time-bearing types.
- date YYYY-MM-DD; localtime/localdatetime ISO with T separator.
- duration P{days}DT{h}H{m}M{s}.{micros}S (months folded in only if present).
- datetime offset ...+02:00; named-zone shows both offset and [Zone].
- Both zoned arms handled: DateTimeOffset and DateTimeZoned.
- New `temporal` golden category.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Point2d/Point3d render with SRID and coordinates:
point({srid: 7203, x: 1.0, y: 2.0}) etc. (Memgraph toString does not
support points, so this is a defined convention).
- Enum already normalised to Value::Enum at the boundary (ADR 0003);
renders as Type::Member (Status::Active).
- New `point_enum` golden category. Rendering seam (03-07) now complete:
every Value arm is real, no provisional/Debug renderings remain.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- core::tabular::render_table via comfy-table (ASCII_FULL, force_no_tty for deterministic goldens): header + content-sized aligned columns. - TableOptions.fit_width fits the table to a terminal width (cells wrap). - RecordStream::collect_capped(cap) -> (rows, overflowed): never holds more than `cap` rows (one extra peeked to detect overflow), so the buffered path is bounded (ADR 0002). row_cap_warning points to streaming formats. - CLI renders a real table and warns on overflow. - Block-golden harness (check_block) + `table` goldens: basic, wide, fit. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- QueryAssembler.push(chunk) -> completed queries; unterminated tail retained and re-scanned on the next feed (so strings/comments spanning feeds work). - Char scanner ignores `;` inside '..'/".."/`..` and // and /* */; honours backslash escapes and doubled-backtick escapes; skips empty queries. - 10 pure unit tests covering each case. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- scan_clauses(query) -> BTreeSet<Clause> over a string/comment-skipping, case-folding word tokenizer: Create, Match, Merge, IndexCreate, IndexDrop, DetachDelete, Delete, Remove, StorageMode. - CREATE INDEX != plain CREATE; DETACH DELETE != plain DELETE. - Keywords inside literals/comments ignored; word boundaries stop `created` matching CREATE; composes across a multiline query. - 8 pure unit tests. Used by parser mode (28) and vertices-first (31). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- RecordStream now pulls lazily in batches (DEFAULT_BATCH_SIZE=1000) from a
connection shared with the Session via Arc<Mutex<Conn>>; never holds more
than one batch, so memory is bounded on arbitrarily large results.
- Session.run no longer eager-pulls; the signature (QueryResult{header,
RecordStream,summary}) is unchanged — no downstream migration (ADR 0004).
- RecordStream::discard() drains the server-side stream (Bolt DISCARD) so the
Session is reusable after a partial/capped read; CLI discards post-cap.
- Buffered variant retained for unit-testing the cap/collect logic.
- Shared Bolt metadata helpers in proto.rs (fields/failure/has_more).
- Integration tests: stream 50k rows incrementally; discard-then-reuse.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Row-oriented RowWriter trait + write_stream driver that streams a RecordStream one Record at a time (bounded memory, ADR 0002). - 22 csv: csv crate; configurable delimiter/quote/escape/double_quote; null is an empty cell, composites reuse tabular rendering; crate handles quoting of cells with delimiters/quotes/newlines. - 23 jsonl: one JSON object per row keyed by column; documented JSON encoding per Value type (graph/temporal/spatial/enum); valid JSONL. - 24 cypherl: replayable statements one per line (DUMP-style), exactly one trailing semicolon (idempotent); round-trip validated later by slice 26. Error gains an Output variant + From<io::Error>. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Decisions from a plan stress-test (no production code change): - ADR 0005: Session guards "one live result at a time" at runtime (keeps the Arc<Mutex<Conn>> cancellation seam) rather than borrow-enforcing it; folded into issue 14 (ResultStillOpen + RESET-on-abandoned). - ADR 0006: parallel import uses worker-pull (N worker Sessions on a shared Batch queue), NOT a connection-pool crate — drops the deadpool-vs-bb8 question. Re-scoped issues 29/30; updated PRD + README. - Line editor: rustyline over reedline (frontend-local, maps to Validator/ Highlighter/Completer/FileBackedHistory). Updated issues 16/18. - Integration-test strategy: shared container per test binary + reset(), recorded in the test harness for slices 12-14+ to adopt. - Floats/null deliberately diverge from Memgraph toString (faithful type display, round-trip) — rationale documented in issue 03. - Dev hygiene: rust-toolchain.toml (clippy/rustfmt components), workspace lints forbidding unsafe (ADR 0001 now compiler-enforced), cargo `lint` alias. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Folds the integration-test-strategy decision into the first slice that adds integration tests, rather than spawning a separate issue.
Session::connect_with carries optional Credentials over scheme:basic and maps a HELLO refusal to Error::Auth. cli resolve_password injects the no-echo prompt so it's pure-tested; main wires rpassword. Integration test authenticates against a credentialed container and asserts wrong creds yield Error::Auth.
ADR 0007 records the rustls+ring backend choice (no OpenSSL; relaxes ADR 0001's
no-C-toolchain line) and the no-verify policy mirroring mgclient REQUIRE. A
MaybeTlsStream enum keeps the Client monomorphic; connect params consolidate into
ConnectOptions { credentials, use_tls }. Integration test generates a self-signed
cert with rcgen, starts an SSL Memgraph, asserts TLS connects and plaintext to
the TLS port fails.
Session::run_with_params binds named params; value::to_bolt encodes Core Values to Bolt, rejecting structural types (node/rel/path/enum) as Error::Parameter. Introduces the one-container-per-binary lease() harness with reset() and converts the session tests to it; auth/TLS keep dedicated containers. Pure encode tests + integration tests for mixed-kind params and a stored-data lookup.
Summary now carries notifications, stats, and residual trailing metadata; it is built from the RUN reply and completed when the record stream drains/discards (absorb_terminal), so QueryResult.summary() reflects post-drain data per ADR 0004. ExecutionInfo lifts Memgraph's cost_estimate/parsing/planning/ plan_execution times. Keys discovered from a live container probe; integration tests assert stats, notifications, exec info, and clean absences.
QueryError carries the full/leaf code; is_retryable() keys off the leaf, not the TransientError tier (spike: a permanent error wore a TransientError code). RUN FAILURE now RESETs so the Session survives a query error. Fatal transport errors reconnect with bounded retries (stored ConnectOptions) and retry once; exhaustion is a terminal Error::Connection. ADR 0005 guard: ResultGuard liveness token makes run() return ResultStillOpen for a live result and RESET-recover an abandoned one. Adds a severable TCP proxy test harness; integration tests cover all five paths.
Give each Buffer tab an identity and make the bar scale. A tab is auto-titled from a short snippet of its content — the originating query of its shown Result-history entry, falling back to the first non-empty editor line, then the Buffer number — truncated to a small width, the active tab highlighted, the Buffer owning the in-flight query marked •. When the tabs exceed the bar width it windows to keep the active tab visible, with ‹/› overflow markers. Titling, windowing, and the click hit-test share one pure layout (WorkbenchState::layout_tabs), cached on the state as tab_hits, so a tab-bar click maps to the right Buffer under windowing and variable title widths. Replaces the old fixed-width numbered tabs and the integer-division hit-test.
Extend the theme beyond highlight categories to the Workbench chrome: - Palette gains border / selection / status slots, overridable through the existing [theme] table and carried by every built-in. The draw consults them for pane/overlay borders, the selected cell/row, and the status bar — no remaining hardcoded Cyan/reverse for these. An absent override reproduces today's appearance (Cyan focused borders, reverse-video selection, unstyled status — the Default sentinel). - A new light built-in joins default/mono, tuned for light-background terminals (darker base ANSI colours, legible comment grey, blue chrome). :set theme light selects it and it is listed among the built-ins. selection_style maps Default→reverse-video and a set colour→background, so themes can recolour the selection without losing the no-colour cue.
The operations that replace the whole editor buffer — auto-format, :load, and history recall — used to rebuild the text-area widget, discarding its undo stack, so a format could not be reverted and recalling over unsaved text lost it irrecoverably. EditorState now keeps a snapshot undo/redo stack: each logical edit (a keystroke, newline, completion, or whole-buffer replace) checkpoints the prior buffer, so a single Ctrl+Z restores it regardless of how many internal text-area edits it made (a replace is otherwise two). Ctrl+Y redoes. Snapshots are cheap for query text and sidestep tui-textarea's per-internal-edit history. Ctrl+Z/Ctrl+Y are editor-pane chords; from the results pane Ctrl+Y still reaches the summary-drawer gesture, so nothing is lost to the collision.
Make the Workbench's gestures discoverable: - Bare ? opens the :help overlay when the editor is empty or the results pane is focused; typing ? into a non-empty query still inserts it, so a ? in Cypher text is never swallowed. - The status-bar hint is regenerated from the live [keys] (status_hint) so it shows the user's actual chords — e.g. a rebound format key — rather than a stale fixed string, and carries a '? help' pointer. - The help overlay already reads the live [keys]; a test now pins that it reflects a rebinding and lists the remapped nav chords and :close.
The :help and cell-detail overlays are scrollable but gave no cue when there was more content off-screen. They now show a ▲ near the top-right when there is content above the view and a ▼ near the bottom-right when there is more below, so it is clear when — and which way — to scroll. Nothing is drawn when the content fits. A wrapped_line_count helper estimates the wrapped height (ratatui's public line_count is feature-gated), driving the indicator. Pure presentation; covered by draw tests for the overflow and fits cases.
Two refinements from a grilling session against the domain model. :close is a Workbench command, not a shared meta-command. CONTEXT.md defines a Workbench command as having no meaning in the line REPL (which reports it unknown), but issue 02 had made it a recognised MetaCommand the REPL reported as 'unavailable'. Removed MetaCommand::Close; the Workbench now parses :close in its own command layer (WorkbenchCommand) ahead of the shared parser, and the REPL reports it as a plain unknown command. :close is also no longer refused mid-query: it always closes the active buffer, cancelling the in-flight query when that buffer owns it (close_buffer fixes up the running-query index across the removal). Ctrl+Y is the editor's redo, not the summary toggle. The editor owns the Ctrl/Alt+letter chords it uses for editing (ADR 0016, narrow reading of the PRD ruling); redo is Ctrl+Y for terminal portability, so the summary drawer moved to Ctrl+N. Undo/redo now bind globally (no focus-scoping) since the collision is gone. Adds ADR 0016 recording the keybinding-ownership boundary.
Plan for round 2: modal Command line (editor is Cypher-only), Tab/Shift+Tab keyspace, Transaction episode model + correlated history, bolder tx indicator, and a clipboard helper process with OSC 52 fallback. New CONTEXT terms (Command line, Transaction episode); ADR 0017 (modal command line) and ADR 0018 (clipboard helper over OSC 52, refining 0015).
The Buffer editor was a dual-purpose surface routing :-lines to the meta parser; that forced Tab to mean two things and made : un-typeable for labels in non-empty buffers. ADR 0017: a modal Command line owns the typed :-vocabulary, the editor holds only Cypher. - New CommandLine state + draw over the status row, owning the cursor while open. - Opens on : while the editor is empty/whitespace-only, or on Ctrl+G anywhere (new rebindable Gesture::OpenCommandLine, default ctrl+g per ADR 0017). Enter runs via the existing Workbench-command / MetaCommand path; Esc cancels; focus returns to where it was. Not part of the focus cycle. - Editor submit no longer routes :-lines to meta — a :-line is Cypher and errors. - handle_meta no longer clears the editor (the command line is a separate surface), so a half-written query survives a mid-composition command. :load still recalls into the editor; :close no longer wipes the last buffer. - Migrated all :-command reducer tests onto the command line; new tests cover open-on-empty vs literal-:, the Ctrl+G opener, run, cancel, and editor-submit-is-Cypher. 207 workbench tests green; lean build + clippy clean.
Make the modal Command line ergonomic (issue 02): - Tab completes a :command name from the existing completion engine — a new CommandVocabulary source over COMMAND_NAMES, reached via Completer::with_command_vocabulary(). A repeated Tab cycles the matching candidates inline; with no candidates it is a no-op (never switches focus). - Up/Down recall past :-commands from a new session-scoped command_history stack, distinct from the Cypher query history that Ctrl+Up/Down recalls into the editor. Stepping past the newest restores the in-progress line. - CommandLine carries the inline completion menu (CommandCompletion) and the recall position; an edit drops the menu. Tests cover command-name completion, cycling, no-candidate no-op, recall in both directions across multiple entries, and the two histories staying distinct. 214 workbench tests green; clippy clean.
…witch Removes Tab's overload in the editor (ADR 0017): Tab no longer cycles pane focus when there is nothing to complete. - Editor Tab is completion-only — opens the popup, or is a no-op with no candidates; it never switches focus. - New rebindable Gesture::SwitchFocus (default Shift+Tab) toggles editor↔results focus when the completion popup is closed; with the popup open, Shift+Tab is caught first as prev-candidate (the mirror of Tab's next). - Results-pane Tab is now a no-op (focus-switch is Shift+Tab). - IO edge normalises crossterm BackTab → Tab + shift (shift forced, since some terminals omit the modifier on BackTab). - Status hint and help reflect Tab: complete / Shift+Tab: focus. Tests: Shift+Tab focus-switch both directions, plain Tab no-op on empty editor, Tab-still-opens-completion, Shift+Tab prev-candidate with wrap. 216 workbench tests green; clippy + lean build clean.
…tory
A query run inside :begin…:commit/:rollback was a bare success whose rows might
since have been rolled away. Introduce the Transaction episode (CONTEXT.md): a
Session-scoped, numbered :begin→end lifetime with per-statement ordinals and a
final disposition.
- Session-scoped tx_episode / tx_ordinal counters; each submission made while a
transaction is open is tagged with {episode, ordinal, Open}. Counters are
global, so ordinals continue across Buffers within one episode.
- :commit/:rollback resolve the episode retroactively across every Buffer's
Result history (open → committed / rolled back), driven by the actual
TransactionApplied outcome via a remembered pending op — so a poisoned :commit
the server rejects does not resolve it; only :rollback does.
- A rolled-back entry keeps its rows but is marked undone. A :connect swap
resolves the aborted episode as rolled back. Autocommit entries are untagged.
- Draw renders 'tx N · stmt k [disposition]' in the results-pane header
(extracted results_title helper).
Tests: ordinals across Buffers, retroactive commit resolution across Buffers,
rolled-back-keeps-rows, poisoned→rolled-back, second-episode numbering, plus
draw tests for the header tag and autocommit-untagged. 224 workbench tests
green; clippy + lean build clean.
The dim [tx] / [tx failed] marker was too easy to miss. Replace it with a loud, glanceable status segment. - New theme UI slots transaction / transaction_failed (extending issue 08), carried by every built-in: amber/red on default, monochrome on mono, blue/red on light (a Yellow block washes out on white). Both overridable via [theme]. - The status bar draws TX N OPEN (open) or TX N FAILED (poisoned) reverse-video + bold so the slot colour reads as a solid block; N is the Transaction episode number (issue 04). Autocommit shows no segment. - status_text no longer carries the dim [tx]; draw_status prepends the segment. Tests: draw segment for open/failed/autocommit, the slot colours resolve over the active theme incl. light, and the theme slots resolve + override. 226 workbench tests green; clippy + lean build clean.
ADR 0015's accepted cost — yank silently no-ops on a terminal that ignores OSC 52 — turned out to be the common *local* case (a Wayland VTE terminal). ADR 0018 refines the mechanism ordering: prefer a local helper process, fall back to OSC 52. No FFI, no new crate (ADR 0001 stands) — just std::process::Command. - New workbench::clipboard module: probe wl-copy → xclip → xsel → pbcopy → clip.exe on PATH, pipe the rendered text to the first present; a helper that exists but fails (or none present) falls back to writing OSC 52. - The IO edge reports the path taken, appending '(wl-copy)' or '(OSC 52 — paste may need terminal support)' to the yank status — a silent no-op becomes a visible, explained outcome. Payload is unchanged (the rendered Value); only where the bytes go changes. - Selection is pure over injected probe/run/fallback closures (copy_via), so the probe order, no-helper fallback, and present-but-fails fallback are tested at the IO seam with no real process or terminal. 442 lib tests green; clippy + lean build clean.
The read-only-off-at-runtime message had drifted between the two Meta-command dispatchers — the REPL named the escape hatch (--read-only / a profile), the Workbench did not — so an operator saw different wording per Frontend. Pull the three cross-Frontend operator-facing strings into pub consts in repl.rs (the existing shared-vocabulary home, alongside SYSINFO_QUERIES): the read-only refusal, the :watch-in-transaction refusal, and the :connect-aborts-transaction warning. Both dispatchers reference them; each still adds its own error:/note: prefix. The refusal rules stay inline (trivially identical), and the two dispatchers stay separate by design (ADR 0010's non-blocking Workbench). Pure hygiene — no behaviour change. 442 lib tests green; clippy + lean clean.
Durable record of the deepening review + grilling pass: three of four candidates rejected on the deletion test / call graph (B and C are correct factoring; D is locality-only), one shipped (A, demoted to a thin string dedupe). FINDINGS.md is the record so a future review does not re-suggest the rejected candidates; report.html is the visual companion (the skill writes it to /tmp, kept here on request).
… 0012) File issue 22: write an inert, fully-commented example config.toml on interactive first run when none exists (TTY + MGCONSOLE_CONFIG_PATH unset + file-missing). Amend ADR 0012 to record the scaffold as first-run population of ~/.mgconsole, not a change to its location or escape hatch.
The one in-flight query was 5 fields scattered down WorkbenchState (run +
running_statement/started/tag, plus running_buffer), kept mutually consistent by
hand across ~6 lifecycle functions; nothing prevented Idle + a stale running_*.
Carry the three per-statement facts inside the variant:
Running { id, statement, started, tag }
so they cannot dangle while Idle — the 'all cleared on idle' invariant is now
structural (the data goes with the variant). RunState loses Copy; id-only callers
go through a new RunState::running_id() accessor, so the new payload never leaks
to them. running_buffer and pending stay top-level — they outlive one statement
(the submission / buffer-identity concern). :o keeps tag: None (no history entry).
Behaviour-preserving refactor: 231 workbench tests green (incl. a new test
documenting the folded payload + accessor); clippy + lean build clean.
Architecture review round 2, candidate 1 (.scratch/architecture-review).
The six overlays (help, cell-detail, export, search, completion, command line) were six independent fields, with 'at most one open' upheld only by discipline. The 'is a modal open' check was triplicated — the key cascade, the mouse ignore-list, and the draw — and the mouse copy had drifted (it omitted search/command_line), so a mouse-click on a result cell while in-result search was open opened the cell-detail overlay *on top of it*: a reachable two-overlay state. Replace the six fields (+ help_scroll/detail_scroll) with one modal: Option<Modal> so 'two overlays open' is unrepresentable. The key dispatch is one match on a Copy ModalKind tag; the draw matches &state.modal and renders exactly one; the mouse is ignored whenever state.modal.is_some() — one check, all six, can't drift — which fixes the bug structurally. drawer (a side panel) and watch (a 'press any key to stop' mode) stay separate; they are not modals. Read/write views (search()/completion()/command_line()/detail()/…) keep the handler call sites close to before while the storage stays the one enum. recall_command uses the inline pattern (disjoint field borrow) where the method accessor would clash with the command-history borrow. 444 lib tests green (incl. a regression test for the two-overlay bug); clippy + lean build clean. Architecture review round 2, candidate 2.
Workbench-reducer sweep. #1 (fold the in-flight query into RunState::Running) and #2 (one Modal sum type for the six overlays, fixing a reachable two-overlay bug) shipped; #3 (drop the Buffer parking dance) deferred with its grouped active_buffer shape recorded. Draw edge + untouched Core came back clean.
…bench switch Design from a grilling session: switch between the two interactive Frontends at runtime over a preserved Session, rather than the startup-only choice of ADR 0010. - ADR 0019: dispatch loop owns a Session-state bundle (params + Settings hoisted in to match the CONTEXT.md Session definition); Frontends return Quit | SwitchTo; :repl/:workbench Meta-commands; capability-gated :workbench (not --plain); refuse-while-busy reusing ADR 0005; lossy round-trip in v1. - ADR 0010: amendment pointer (startup logic stands; choice no longer permanent). - CONTEXT.md: Frontend and Session entries updated to reflect runtime switching and the params/Settings ownership the code now matches. - .scratch/frontend-switch/: issues 01-03 (ready-for-agent) + 04 view-parking follow-up (needs-triage).
…rst interactive run On the first interactive run with no config.toml and no MGCONSOLE_CONFIG_PATH override, write a fully-commented EXAMPLE_CONFIG template to the resolved default path so the format is discoverable. The template is inert (parses to Config::default(), like a missing file) until the user uncomments it. - config::EXAMPLE_CONFIG const + scaffold_example_config() write-if-missing, mirroring history::prepare_history_dir; a unit test proves the template stays inert and that a second run never clobbers an edited file. - main::scaffold_config_on_first_run() gates on the default path (skips when MGCONSOLE_CONFIG_PATH is set) and is called only from the interactive path, so piped/run/-c/NDJSON invocations never write. A write failure is a warning.
… files (ADR 0020) Replace the single tool-managed queries.toml with a tool-owned ~/.mgconsole/queries/ directory of plain <name>.cypher files. The directory is the source of truth — no index — so saved queries are greppable, git-versionable, hand-editable, and the same directory doubles as a :source library (a named query is a loadable script). - queries.rs: resolve_queries_dir (was resolve_queries_path); NamedQueries keeps an in-memory mirror loaded from the directory + per-file sync_file() that writes one <name>.cypher (save) or deletes one file (forget), never a whole-store rewrite, so a crash mid-write leaves at most one bad file. Lenient parse (ADR 0020): an oddly-named or unreadable file is recorded as a warning surfaced by :saved, never fatal. Names with path separators are rejected (is_valid_name) so a name can't escape the dir. - :save/:saved/:load/:forget unchanged in surface; the REPL calls sync_file and the Workbench reducer emits Effect::PersistQuery(name) (was the whole-store PersistQueries). - queries.toml removed from the resolver and code; QUERIES_SUBDIR/QUERY_EXT replace QUERIES_FILENAME, with comments recording that ADR 0020 superseded the file. - CONTEXT.md Named-query definition + ADR 0020 record the format change. The verbs behave identically in the REPL and Workbench. No migration shim (pre-release).
… auth Add an environment-variable password path so an authenticated connection can be made non-interactively without leaking the password to shell history or ps. resolve_password now takes the flag, env, and profile passwords as separate sources rather than one pre-merged value, so MGCONSOLE_PASSWORD slots between the explicit --password flag and a profile password: --password flag > MGCONSOLE_PASSWORD > profile password > hidden prompt The first non-empty source wins. An empty username (anonymous) ignores the env var entirely — no prompt, no auth. The env value is read once at the call site and passed in, keeping resolve_password pure and testable. Deliberately not an OS keychain (fragile over SSH/containers/CI). Documented in the example config and the resolver doc; never echoed or logged. Unit tests cover the full precedence and the anonymous-ignores-env case.
…rved Session
Stand up the dispatch loop above the two interactive Frontends (ADR 0019) and wire
the first direction: typing :repl in the Workbench drops to the line REPL with the
same live Session underneath — connection, open Transaction, params, Settings,
active Database, and Read-only mode all intact.
- frontend::Outcome { Quit, SwitchTo(Frontend) }: each Frontend's run-function now
returns this instead of Result<()>; main's dispatch loop exits on Quit or
re-enters the named Frontend on SwitchTo.
- Session bundle owned by the loop: the :param store and Settings are hoisted out
of repl::run_loop and WorkbenchState into &mut-lent locals (CONTEXT.md's Session,
made honest). The Session is shared via Arc<Mutex<Session>> so the connection/tx/
read-only/database stay live across the switch; SessionRunner locks it per op.
- :repl is a cross-frontend Meta-command: in the Workbench it emits Effect::SwitchTo
(refused while a query is in flight with the one-live-result "cancel first"
guard, ADR 0005, without aborting the query); in the REPL it is a gentle no-op
("already in the REPL"). Added to the Workbench :help and command vocabulary.
- The Workbench aborts and awaits every Session-holding background task (schema
fetch, param eval) before returning, so no straggler races the REPL's queries.
- HistoryFile is Clone so the loop lends a copy to each re-entered Frontend.
Unit tests cover the :repl parse, the REPL no-op, and the Workbench switch effect +
busy refusal. Up-switch (:workbench/:tui) and editor-text carry are issues 02/03.
Close the runtime Frontend-switch round-trip (ADR 0019): typing :workbench (or
its :tui alias) in the REPL raises the full-screen Workbench over the same live
Session — connection, open Transaction, params, Settings, active Database, and
Read-only mode all intact, in one fresh Buffer.
- MetaCommand::Workbench + meta_command parses :workbench/:tui, but only under the
`tui` feature: a lean REPL-only binary has no Workbench, so the word reads as an
unknown command (mirrors the feature-gated Workbench Frontend variant).
- Capability gate, not the --plain flag: ReplConfig gains `supports_tui` (a capable
tty + the feature), threaded from main. dispatch_meta returns MetaFlow::Switch
(→ Outcome::SwitchTo) when capable; on an incapable terminal (TERM=dumb / non-tty)
it is refused with a clear message and the REPL carries on (the universal floor).
Independent of --plain, so launching --plain on a capable terminal upgrades.
- :workbench typed in the Workbench is a gentle no-op ("already in the workbench"),
the mirror of :repl in the REPL. A switch command inside a :source batch is a
no-op (no mid-batch switch).
- :workbench listed in the REPL :help (feature-gated) and the Workbench command
vocabulary.
Unit tests cover the parse (and the non-tui not-recognised contract), the capable
switch, the incapable refusal, the help listing, and the Workbench no-op.
The "query editor is connected" headline (ADR 0019): a draft you were composing follows you across a Frontend switch instead of being lost. - Down (Workbench → REPL): WorkbenchExit carries the active Buffer's editor text; the dispatch loop hands it to run_loop as the carried-down `initial`, which seeds the first prompt's input line (a multi-line draft survives intact). The meaningful direction — a half-drafted query arrives ready to edit or run. - Up (REPL → Workbench): run_loop returns its current input line alongside the Outcome; the loop seeds the one fresh Buffer via WorkbenchConfig::initial_editor (new EditorState::with_text). Usually empty (the :workbench command consumed its own line) — carried for symmetry, not special-cased. The dispatch loop holds a single `carry` String threaded between Frontends. Only the active editor text travels; inactive Buffers and per-Buffer Result history stay discarded (lossy round-trip; non-destructive view-parking is issue 04). Unit tests cover the down-seed (incl. multi-line and the empty/no-spurious case), the empty up-carry on a :workbench switch, and the Workbench seeding the fresh Buffer from a carried draft (incl. that it is runnable, not a frozen seed).
Unblocked now that :repl/:workbench and editor-text carry have landed. Add a codebase-grounded agent brief: Buffers are already purely Frontend-local, so view-parking is the whole-Vec version of the existing take_active/install_active move and the "rebind to live Session" reconciliation is automatic.
…witch
Make Workbench → REPL → Workbench round-trips non-destructive (ADR 0019 follow-up):
the whole Workbench view — every Buffer (editor text + per-Buffer Result history)
and the active index — is parked across the switch and restored on re-entry, rather
than rebuilding one fresh Buffer.
- WorkbenchView { buffers, active } parked by the dispatch loop. WorkbenchState
gains detach_view/install_view — the whole-Vec form of the existing
take_active/install_active per-Buffer park/restore.
- WorkbenchExit carries view: Some(detach_view()) on a SwitchTo, None on Quit
(carry read before detach, which moves the active editor out). workbench::run
takes an Option<WorkbenchView> and installs it over the freshly-built state.
- main's dispatch loop holds parked_view: on entry a parked view restores (and the
empty up-carry must not clobber the restored active Buffer's text), else the one
Buffer is seeded from the carry (issue 03); on exit parked_view = exit.view.
Buffers hold no session state, so reattaching onto the live Session needs no
per-field reconciliation — the ADR's feared "rebind to a snapshot" can't arise.
Also rebind the transaction marker from the live Session on entry (new
WorkbenchConfig.tx via Session::transaction_state), so a transaction opened before
a switch shows on re-entry instead of reading as autocommit. The active-Database
marker rebind is a documented small follow-up (Session exposes no db getter).
Tests: a reducer-level park→install round-trip preserves buffers/history/active and
the inactive Buffer's text; the tx marker seeds from config on entry. ADR 0019
updated to record the follow-up shipping.
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.
No description provided.