Postguy
Contributing

Architecture

The layer table, the rules that hold across crates, the workspace on disk and the measured design decisions.

Layer table (enforced by cargo xtask layers)

A crate may depend only on lower layers.

LayerCrateRole
L0postguy-modelPlain data and serde: ids, collections, folders, requests (HTTP/gRPC/WS), key-value rows, bodies, auth configs, settings, environments, examples, history. No I/O. Unknown fields are preserved in extras.
L0postguy-utilSize-capped readers, depth-limited JSON walker, ring buffer, fractional ordering, atomic file helpers, time (local-time formatting through jiff; the offset falls back to UTC when the system zone is unreadable).
L1postguy-varsVariable scopes and precedence, {{var}} resolver (depth limit, cycle guard), dynamic variables, secret masking, {{% %}} body shortcodes (shortcode). Pure.
L1postguy-urlkitURL parse/build, per-component percent-encoding, query/URL two-way sync, path variables, host matching. Pure.
L2postguy-authPure signing and credential algorithms. A canonical request goes in; headers and params come out. No sockets.
L2postguy-interopImport/export (Postman v2.0/v2.1, environments, cURL, OpenAPI, HAR, Bruno folders) with ImportReport. Bounded sizes and nesting.
L2postguy-storeWorkspace on disk: atomic writes with .bak, lock file, trash, history, session, settings, secrets split.
L3postguy-httphyper-direct connector: connect/TLS/timing, redirects, compression, cookie jar, proxy, auth retries, streaming, cancellation.
L3postguy-grpcprotox compile, descriptor pool, reflection, dynamic invoke, examples, status/trailers.
L3postguy-wstokio-tungstenite WebSocket client: handshake, TLS, frames, reconnect policy, message log.
L3postguy-scriptrquickjs (QuickJS) sandbox with the pm API, its superset pg (plus Bruno's bru/req/res/test/expect), request assertions, asset-backed require with read-only fs/path/Buffer/crypto.randomUUID shims, Chai-style pm.expect and hard wall-clock, memory, stack and cancel limits. All of it is Rust closures. Settings.disable_scripts is the off switch.
L3postguy-snippetsCode generation (cURL, fetch, requests, Go, reqwest, PowerShell, axios, grpcurl). Pure.
L3postguy-testkitLoopback servers for tests only. A dev-dependency, never shipped.
L4postguy-runnerCollection runs: plans, iterations, CSV/JSON data files, setNextRequest sequencing, results model, CLI/JSON/JUnit/HTML/CSV/XLSX/PDF reporters (one ReportTable feeds the CSV, XLSX and PDF writers). Pure.
L6postguy-engineFaçade and session: workspace handle, edits/undo, send pipeline (12 steps), per-collection assets, Bruno folder import, runtime variables, command registry, console, history, runtime.
L7postguy-platformOS services. Today: open_url, save_export, pick_data_file and pick_import_paths (crates/platform/src/lib.rs). Planned: directories, reveal, clipboard, system proxy and system font fallback.
L7postguy-ui-eguiThe egui shell (eframe + glow): widgets and screens. Depends on postguy-vars (shortcode highlighting) and postguy-util (times).
L7postguy-automationHeadless tool table over the engine, CLI backend, opt-in UI control channel. The opt-in MCP server is planned, not shipped.
appspostguy, postguy-cliDesktop app (with the opt-in --control server in apps/postguy/src/control.rs). Headless CLI with the verbs tools, call, run (--script / --collection), ui and the dev-only fuzz-target (apps/postguy-cli/src/main.rs). Import, export and snippets go through the call / run --script tools. postguy-cli mcp does not exist yet.

Tooling: xtask (build system, gates, fuzz, icons/logo, size) is outside the layer system.

Rules that hold everywhere

  • Never crash (AGENTS.md 4):
    • Use Result everywhere. No unwrap or panic in non-test code.
    • unsafe_code = "forbid".
    • Use checked arithmetic on input-sized values.
    • Bound recursion and allocations.
    • Use catch_unwind at import, script and command entry points.
    • panic = "unwind".
  • Layering: a crate may depend only on lower layers. Nothing below L7 reaches egui, winit, eframe or rfd. The layers gate walks the normal-dependency closure over cargo metadata.
  • Concurrency:
    • engine owns one tokio runtime on dedicated threads.
    • The UI receives events and repaints once per batch.
    • Every long operation takes a cancellation token and wraps panics into error rows.
  • Workspace on disk: one folder.
    • Contents: postguy.json, collections/<id>.json, environments/, globals.json, protos/, files/, assets/<collection id>/, history.jsonl, .trash/, .postguy/{secrets,cookies,responses,runs,lock}.
    • Writes are atomic. There is a .bak file and corrupt-file recovery.
    • Settings, session and the recent-workspaces list live in .postguy-config/{settings,session,recent}.json inside the workspace (apps/postguy/src/main.rs).

Decisions made with measurements

  1. Renderer: eframe glow renderer. It is smaller and warms up faster than wgpu.
  2. Response bodies: bytes, plus an off-thread line index, plus show_rows virtualisation.
  3. HTTP stack: hyper-direct connector. It gives control of timings, redirects, header order and cancellation. tonic shares the same hyper stack.
  4. Scripting: rquickjs (QuickJS, MIT). The owner approved it for its memory limit, interrupt handler and stack limit.
    • boa_engine exposes none of these and is not used.
    • rust-alloc stays off because it turns set_memory_limit into a no-op.
    • Windows x86 and FreeBSD build the bindings with bindgen, because no pregenerated ones ship for those targets.
  5. Icons and logo: build-time path data plus painter strokes. No SVG renderer is shipped.
Copyright © 2026