| title | Development |
|---|---|
| description | Build, test, lint, and code conventions. |
The macOS and Linux builds require Go 1.27+, Bun 1.3.14+, Node.js (20.19+ on
20.x, 22.13+ on 22.x, or 24+), and a C/C++ compiler. The Make targets install
the pinned browser dependencies when web/package.json or web/bun.lock
changes, then embed the production UI in the Go binary; Node runs the embed
validator in that path. On Debian/Ubuntu also install libsqlite3-dev, which
provides the sqlite3.h header needed to compile the default sqlite_vec
extension.
# Debug build
make build
# Release build (optimized, stripped)
make build-release
# Install to ~/.local/bin or GOPATH
make installUse the PowerShell helper from the repository root to compile the Go binary.
It selects the host architecture automatically and embeds assets already in
internal/web/dist; it does not build the browser application.
For a binary with the Web UI, first run make web-embed in an MSYS2 shell
with GNU Make, Bun, and Node.js available. This builds and validates the browser
assets. Then run the PowerShell helper:
# Debug Go build
.\scripts\build.ps1
# Optimized, stripped Go build
.\scripts\build.ps1 -ReleaseGo and MSYS2 are required because msgvault uses CGO. Install the compiler for your Windows architecture:
# Windows AMD64 (run from PowerShell)
C:\msys64\usr\bin\pacman.exe -S --needed mingw-w64-x86_64-toolchainFor Windows ARM64, install CMake and Ninja from an MSYS2 CLANGARM64 shell:
pacman -S --needed mingw-w64-clang-aarch64-cmake \
mingw-w64-clang-aarch64-ninjaThe first ARM64 build downloads a checksum-verified LLVM-MinGW toolchain,
compiles the pinned DuckDB native library, and caches both under
%LOCALAPPDATA%\msgvault\build-cache; subsequent builds reuse them. Set
MSGVAULT_BUILD_CACHE to use another cache location, or pass -RebuildDuckDB
to rebuild the cached library.
The repository's Dockerfile builds the embedded Web UI and the msgvault
binary. To build a local image and run its runtime checks:
docker buildx build --load --tag msgvault:dev .
scripts/smoke-container.sh msgvault:devFor portable image archives, Docker Bake provides an oci target:
docker buildx bake ociIt exports dist/amd64.oci.tar and dist/arm64.oci.tar for Linux AMD64 and
ARM64. The builder needs support for both target architectures, either through
native workers or emulation. Override OCI_OUTPUT_DIR to change the output
directory. VERSION, COMMIT, and BUILD_DATE set the binary metadata;
OCI_VERSION and REVISION set the image's version and source-revision labels.
Defaults identify a development build.
Image builds run checks against a temporary empty archive: database
initialization, a DuckDB query, and the embedded Web UI and its JavaScript
asset. scripts/smoke-container.sh repeats these checks in the loaded image
with networking disabled. These commands build and check local artifacts; they
do not publish them.
Repository-owned release-publishing workflows and the old release/tagging scripts have been removed. Pushing a tag no longer invokes those publishers. The Docker inputs, local build commands, installers, and ordinary CI remain available.
# Run all tests
make test
# Verbose output
make test-vAll Go test runs need -tags "fts5 sqlite_vec"; the Make targets supply these
automatically. Use assert and require from testify, with expected values
first. See AGENTS.md
for repository testing rules.
Use testing/synctest bubbles for work owned by the test process, including
goroutines, channels, timers, tickers, and fakes. Advance virtual time with
synctest.Sleep and wait for durable state with synctest.Wait.
Keep real budgets for PostgreSQL and SQLite locks, database clocks, network requests, subprocesses, DuckDB, and operating-system events. Name retained sub-second testify budgets so their event and owner are clear.
The helper check rejects bare totals below one second in Eventually,
Eventuallyf, EventuallyWithT, EventuallyWithTf, Never, and Neverf.
Named budgets and variables stay outside this rule. Virtual sleeps are valid
inside a bubble. CI runs this check on Ubuntu, so Windows-only test files still
need Windows validation.
make lint and make lint-ci build a pinned golangci-lint with Kit's
kennlint plugin and run its sleeptest check. The check rejects time.Sleep
in tests outside a synctest.Test bubble. A kept real wait carries
//nolint:kennlint // <what it waits for> on the sleep line.
MSGVAULT_TEST_DB=postgres://... runs PostgreSQL-backed
tests. pgvector tests require a PostgreSQL instance with the vector
extension and the pgvector build tag.
The PostgreSQL deadlock tests also require permission to set
deadlock_timeout. They defer the blocker transaction's deadlock detector
so the write under test is the deadlock victim. For a non-superuser test
role, have the database administrator run:
GRANT SET ON PARAMETER deadlock_timeout TO test_role;Replace test_role with the role in MSGVAULT_TEST_DB. This parameter grant
is sufficient; the test role does not need superuser access.
There are two PostgreSQL configurations to cover: the pgvector build
(make test-pg) and the shipped build, which has no pgvector tag
(make test-pg-shipped). Run make test-pg-both rather than both of those —
the tag changes the test binary of only the packages listed in
PG_SHIPPED_ONLY_PKGS in the Makefile, so the second full run would reprove
the first. Each test binary builds the schema once into a template — a
SQLite file copied per test, or a PostgreSQL template database cloned per
test with CREATE DATABASE ... TEMPLATE (internal/testutil/sqlite_template.go,
internal/testutil/pg_template.go) — instead of replaying InitSchema() for
every fixture. Nothing runs in the background, so no fixture ever issues a
schema statement while a test body is running. Each database is still private
to its test, still produced by the same InitSchema() path, and still dropped
on cleanup. A PostgreSQL template is owned through a session advisory lock the
server releases when the binary exits, so the next binary reclaims whatever an
earlier one left behind; a role without CREATEDB falls back to a private
schema in the configured database.
make test automatically overlaps the CLI, store, API, and query package shards
with the remaining SQLite packages when at least 32 CPUs and 64 GiB of available
memory are detected. The planner reserves four test-process slots for the
unsharded remainder, then divides the remaining slots across the sharded
packages, up to 16 shards each. Each slot budgets two Go execution threads
(GOMAXPROCS=2) and a 2 GiB memory allowance. For four sharded packages, a
32-CPU budget permits three shards per package; 128 CPUs permits fifteen,
provided memory also permits them. This changes scheduling, not test coverage.
The planner emits the per-process and remainder settings with its shard count,
so execution uses the same aggregate budget. Shard builds use go -p=1; the
remainder uses go test -p=4. Memory allowances guide scheduling and do not
enforce per-process limits, including native allocations.
Detection accounts for CPU affinity and GOMAXPROCS. On Linux it also accounts
for visible cgroup v2 ancestor CPU quotas and remaining memory under both hard
and soft limits. macOS uses available system memory. Smaller budgets, cgroup v1,
other platforms, and unreadable limits retain the standard schedule. Detection
is a snapshot, not a reservation of resources against other workloads.
Use TEST_PROFILE=standard to disable automatic scaling. Setting TEST_SHARDS
explicitly also retains sequential package jobs with the requested shard count.
The PostgreSQL targets keep their existing connection-oriented concurrency
limits; setting MSGVAULT_TEST_DB disables automatic scaling in make test too.
CI's explicit test-unsharded and package-shard jobs keep their existing layout.
# Format code
make fmt
# Run linter (builds the pinned golangci-lint with Kit's plugin; needs git)
make lint
# Check for issues
go vet ./...Use msgvault eval to compare keyword, semantic, and
hybrid results against queries and relevance ratings you supply. Keep the
archive, topics, and ratings the same when comparing runs. The command reports
ranking quality and query timings; it does not create the ratings for you.
The lossless vCard 2.1/3.0/4.0 codec vendors the IANA vCard Elements registry
under internal/vcard/registry/data. Registry checks are deliberate networked
maintenance commands, not ordinary CI steps:
# Report whether the upstream registry differs from the vendored snapshot.
make vcard-registry-check
# Fetch, validate, and atomically update the snapshot for review.
make vcard-registry-updateThe codec preserves ordered properties, source spelling, parameter quoting, unknown extensions, and raw values. Keep new registry elements covered by an explicit handling declaration so an upstream addition cannot be silently ignored.
- Web UI: Svelte with TypeScript, generated OpenAPI types, and components from the shared UI toolkit
- TUI: Bubble Tea for model/update, lipgloss for styling
- Database: All DB operations through the
Storestruct (internal/store) - Error handling: Return
error, wrap with context viafmt.Errorf - Tests: Table-driven tests
- Cancellation: Context-based cancellation for long operations
- Encoding: Charset detection via
gogs/chardet, conversion viagolang.org/x/text/encoding
- Never use
SELECT DISTINCTwith JOINs: useEXISTSsubqueries instead (semi-joins) EXISTSis faster (stops at first match) and avoids duplicates at the source
Instead of:
SELECT DISTINCT m.id FROM messages m
JOIN message_recipients mr ON mr.message_id = m.id
WHERE mr.recipient_type = 'from' AND ...Use:
SELECT m.id FROM messages m
WHERE EXISTS (
SELECT 1 FROM message_recipients mr
WHERE mr.message_id = m.id
AND mr.recipient_type = 'from' AND ...
)| Library | Purpose |
|---|---|
svelte |
Embedded analytical Web UI |
orval |
Typed browser API client generation |
cobra |
CLI framework |
charmbracelet/bubbletea |
TUI framework |
charmbracelet/lipgloss |
TUI styling |
mattn/go-sqlite3 |
SQLite with CGO (FTS5) |
duckdb/duckdb-go/v2 |
DuckDB driver for Parquet |
gogs/chardet |
Character set detection |
golang.org/x/text |
Text encoding conversion |
The site has three reading levels: the product overview at /, the archive
lifecycle at /guide/, and task guides and references at /docs/. Keep
technical setup and command details in the documentation tier.
From the repository root:
make docs-install
make docs-build
make docs-checkmake docs-check validates Markdown, builds the real site with its asset
branches, and checks pages, links, metadata, and redirects. Use make docs-serve
to inspect the complete site at http://127.0.0.1:8000.
See the documentation contributor guide for content ownership, historical designs, and media maintenance.
Join the msgvault Discord server to discuss development, ask questions, or share feedback.