[package]
name = "inkhaven"
version = "1.4.2"
edition = "2024"
# Rust 1.85 stabilised edition 2024. Bump alongside any toolchain-only
# feature usage (let-else / chained-comparisons / etc. are already stable).
rust-version = "1.85"
license = "Apache-2.0 AND MIT"
description = "Inkhaven — TUI literary work editor for Typst books"
authors = ["Vladimir Ulogov <vulogov@gmail.com>"]
repository = "https://github.com/vulogov/blackInkhaven"
homepage = "https://github.com/vulogov/blackInkhaven"
documentation = "https://github.com/vulogov/blackInkhaven/tree/main/Documentation"
readme = "README.md"
keywords = ["tui", "editor", "typst", "writing", "ai"]
categories = ["command-line-utilities", "text-editors", "text-processing"]

# `cargo publish` packaging filter. Keeps the crate tarball
# small + focused on what a `cargo install inkhaven` consumer
# actually needs to compile and run the binary. Without
# this, the Book/ PDF (≈7 MB), screenshots (1+ MB each),
# rendered artefacts, and the entire Documentation/ tree
# would get bundled — pushing the crate past crates.io's
# default per-file ceilings and the practical 10 MB total
# soft-cap, and bloating the cargo-binstall fallback path
# for users without prebuilts.
#
# Patterns are glob-style relative to the crate root. The
# Book of Inkhaven companion volume lives in git for readers
# and gets re-rendered on every release tag — it does NOT
# need to ship inside the crate.
exclude = [
    # Companion book — typst sources, markdown mirror, the
    # rendered PDF, every screenshot PNG, the cover art.
    "Book/**",
    # Documentation/ ships in the crate — the markdown
    # tutorials + release notes are small (~800 KB
    # uncompressed, ~150 KB after gzip) and useful to
    # `cargo install` consumers without a clone. The only
    # carve-out is rendered PDFs of those docs, which are
    # ephemeral local builds and should never travel inside
    # the crate.
    "Documentation/**/*.pdf",
    # CI workflows + per-machine editor / agent state.
    ".github/**",
    ".claude/**",
    ".vscode/**",
    ".idea/**",
    # macOS / shell scratch.
    ".DS_Store",
    "**/.DS_Store",
    # Rendered artefacts from `Ctrl+B O` takes that ended up
    # at the repo root during development. Same patterns the
    # top-level .gitignore protects against.
    "/*.pdf",
    "/*.epub",
    # PDF-1 imposition proof corpus — a fake book + its compiled /
    # imposed PDFs, kept for local verification only. Never ships in
    # the crate.
    "proof_corpus/**",
    # NB: do NOT exclude `*.png` globally — `logo.png` at the
    # repo root is `include_bytes!`'d into the binary; the
    # build breaks without it. assets/* is also untouched
    # because every file there is loaded via include_str! /
    # include_bytes!.
]

# Distribution model: binary releases via GitHub Actions
# (`.github/workflows/release.yml`) on tag push. Users install with:
#   * `cargo install --git https://github.com/vulogov/blackInkhaven`
#   * `cargo binstall inkhaven` (prebuilts; see [package.metadata.binstall])
#   * Direct download from https://github.com/vulogov/blackInkhaven/releases
#
# `cargo-binstall` metadata: tells `cargo binstall inkhaven` where to
# fetch a prebuilt for the current host triple. The asset filenames
# below must match what `.github/workflows/release.yml` uploads.
[package.metadata.binstall]
pkg-url = "{repo}/releases/download/v{version}/{name}-{target}{archive-suffix}"
bin-dir = "{name}-{target}/{bin}{binary-ext}"
pkg-fmt = "tgz"

# Windows override kept commented so it's a one-line revert when the
# windows-latest matrix entry comes back in release.yml. While disabled,
# `cargo binstall inkhaven` on a Windows host correctly errors with
# "no prebuilt for target" — users on Windows can still
# `cargo install --git`.
# [package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
# pkg-fmt = "zip"

[[bin]]
name = "inkhaven"
path = "src/main.rs"

[dependencies]
# Storage / search. Inkhaven owns its own slim storage layer at
# `src/storage/` — DuckDB-backed metadata + blob stores, an HNSW vector
# index via `vecstore`, and a fastembed wrapper for multilingual
# embeddings. This module replaces what used to be a dependency on
# `bdslib` (vendored at vendor/bdslib/), shedding ~300+ transitive
# crates that inkhaven never reached (Tantivy, augurs, axum, …).
duckdb = { version = "1", features = ["bundled", "r2d2"] }
fastembed = "5"
vecstore = "1"
r2d2 = "0.8"
parking_lot = "0.12"

# CLI
clap = { version = "4", features = ["derive", "wrap_help"] }

# Config (HJSON requirement)
serde-hjson = "1.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Async runtime (required by genai)
tokio = { version = "1", features = [
  "macros",
  "rt-multi-thread",
  "fs",
  "io-util",
  "signal",
  "sync",
  "time",
] }
tokio-stream = "0.1"
futures-util = "0.3"

# TUI — pinned to ratatui 0.29 because tui-textarea 0.7 hasn't bumped to 0.30 yet
ratatui = "0.29"
crossterm = "0.28"
tui-textarea = { version = "0.7", default-features = false, features = [
  "crossterm",
  "ratatui",
  "search",
] }

# Syntax highlighting. Pinned to 0.20 because the in-tree Typst grammar
# under `src/grammar/` is generated against tree-sitter 0.20's parser
# ABI, and the `Language` type is ABI-incompatible across major
# versions. Bump alongside any grammar refresh.
tree-sitter = "0.20"
tree-sitter-highlight = "0.20"

# AI clients (one crate covers Gemini, DeepSeek, OpenAI, Anthropic, …)
genai = "=0.6.0-beta.20"

# Bund scripting language (Vladimir's own VM stack). Exact-pinned to
# the versions inkhaven was built against — bump in lockstep when
# refreshing the scripting surface. See `src/scripting/`.
bundcore = "=0.7.0"
bund_language_parser = "=0.14.0"
rust_multistackvm = "=0.38.0"
rust_dynamic = "=0.49.0"
# bundcore / rust_multistackvm use easy_error for their function-pointer
# `Result<_, Error>` signatures; we need a direct dep to construct that
# error type at the FFI boundary in `scripting::stdlib::ink`.
easy-error = "1"

# Misc
anyhow = "1"
thiserror = "2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
directories = "6"
uuid = { version = "1", features = ["v7", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
arboard = "3"
slug = "0.1"
walkdir = "2"
regex = "1"
# PDF management + imposition (PDF-1). Pure-Rust, audited: no external
# apps / native libs (the only -sys crate in the tree, core-foundation-sys,
# is already vendored). `lopdf` is trimmed with default-features=false to
# drop the redundant `jiff` date lib — inkhaven already has time + chrono.
# `barcoders` (EAN-13) is kept rather than hand-rolling the encoder.
lopdf = { version = "0.41", default-features = false, features = ["chrono", "rayon", "time"] }
barcoders = "2"
# Snowball stemmers (English, Russian, and 17 other languages). Used by the
# editor's Place/Character highlight overlay so a paragraph named
# "Москва" lights up "Москве", "Москвою", etc.
rust-stemmers = "1.2"
unicode-segmentation = "1"
# 1.2.12+ — character-n-gram language detector used by the
# `editor.prompt_language_mode = "paragraph_detected"` mode in
# `App::active_prompt_language`. Offline, no network, ISO 639-3 →
# `iso_from_alpha3` maps to inkhaven's five supported languages
# (en/ru/fr/de/es). Cached on `OpenedDoc.detected_language` and
# only consulted when the paragraph has at least
# `editor.prompt_language_detection_min_chars` of non-whitespace
# text — whatlang is unreliable on very short inputs.
whatlang = "0.16"
# Markdown parsing for the AI pane's rendered output, plus the
# markdown→Typst translator that runs when the user applies an AI result to
# a paragraph via r/i/t/b.
pulldown-cmark = { version = "0.13", default-features = false }
# Zip backup / restore commands and the TUI auto-backup-on-exit path.
# Also reused by the `inkhaven export epub` pipeline to assemble the
# EPUB3 container — same `deflate` feature flag, no extra dep cost.
zip = { version = "2", default-features = false, features = ["deflate"] }
# Pure-Rust Typst→LaTeX converter for `inkhaven export tex` and the
# Ctrl+B O extra-format pipeline. `default-features = false` drops
# the bundled clap CLI; we only consume the library API
# (`tylax::typst_document_to_latex` / `typst_to_latex`).
tylax = { version = "0.3", default-features = false }
# Line diff for the F6 snapshot-diff view (press `V` on a row in
# the snapshot picker). Pure Rust, Apache-2.0 — uses Myers + LCS
# under the hood. `text` feature is what exposes `TextDiff` (its
# default-features pull in unicode + bytes which we don't need).
similar = { version = "2", default-features = false, features = ["text"] }
# Scrivener import pipeline (1.2.4+).
# `quick-xml` parses the `.scrivx` binder XML (MIT).
# `rtf-parser-tt` is a maintained fork of rtf-parser with
# smart-quote / em-dash support that's essential for prose
# imports (MIT). Both pure Rust — single binary stays single.
quick-xml = "0.36"
rtf-parser-tt = "0.5"
# Human-friendly duration parsing for the auto-backup `max_age` config knob.
humantime = "2"
humantime-serde = "1"
# Typewriter sounds (Enter / focus-out). The output stream is initialised
# lazily and silently skipped when the host has no audio device, so a
# headless build (CI, SSH session without audio forwarding) still works.
# Linux release builds need `libasound2-dev` at build time.
rodio = { version = "0.20", default-features = false }
# 1.2.9+ — text-to-speech for Ctrl+B S / greeting / goodbye.
# Originally targeted at the `tts` crate (tts-rs) for cross-
# platform coverage, but its AVFoundation backend exhibits a
# per-process state bug on at least one macOS version: the first
# `Tts::default()` engine speaks once, every subsequent speak()
# returns Ok with a fresh utterance id but produces no audio.
# Drop-and-recreate fails entirely with "Operation failed".
# Falling back to `/usr/bin/say` subprocess (see `tui::say`) —
# each invocation is a fresh process, no shared state, reliable.
# Crate removed; non-macOS platforms fall back to the
# "TTS is macOS-only" modal until a different cross-platform
# backend lands in 1.3+.
# Image preview in the editor pane. The Picker is created lazily by
# querying the host terminal's graphics-protocol support; on terminals
# that don't speak kitty / sixel / iterm2 the half-block backend is the
# fallback. Disabled at the application level by `images.preview_enabled`.
ratatui-image = "5"
# In-memory decoder for the image bytes we feed to ratatui-image.
# Limit codecs to the formats we accept on import to keep the binary
# from pulling in TIFF / OpenEXR / etc.
image = { version = "0.25", default-features = false, features = [
  "png",
  "jpeg",
  "gif",
  "webp",
] }
# WORLD-4 P1 — the world-simulation geology layer. Both pure-Rust, MIT/Apache
# (RFC §11). `noise` synthesises the procedural heightmap; `delaunator` gives the
# Delaunay triangulation behind the Voronoi plate boundaries. `image` (above) is
# reused for DEM/heightmap import. (Determinism uses a tiny in-tree seeded PRNG,
# not `rand`, to keep `(definition, seed) -> identical output` airtight.)
noise = "0.9"
delaunator = "1"
# typst-syntax — Phase 1 of the typst-as-library plan (1.2.5+). Pulls
# in the Typst parser only (no eval, no layout, no render, no fonts).
# Used by `src/typst_check.rs` to surface parse errors at the source
# line/col while the user edits.
typst-syntax = "0.14"
# typst — full compiler umbrella for the in-process engine
# (Phase 2+3+4 combined). Activated at runtime by
# `typst_compile.engine = "inprocess"` in HJSON; the default
# `external` engine still shells out to the host's `typst` binary.
# Bringing it in unconditionally so the HJSON switch is a pure
# runtime decision — no rebuild required to flip engines.
typst = "0.14"
# typst-pdf — PDF emitter. Pairs with `typst::compile::<PagedDocument>()`
# to produce the same `.pdf` bytes the external CLI would.
typst-pdf = "0.14"
# typst-render — rasterise a compiled `Frame` to a PNG bitmap. Used
# by the Ctrl+V R "render paragraph" feature to paint the in-process
# compile's first page into the editor preview pane (via
# ratatui-image) and to write a full-DPI PNG on `S`.
typst-render = "0.14"
# resvg — rasterise hand-built SVG into a PNG bitmap. Used by
# the Ctrl+V W "story view" path: `src/story_view.rs` computes a
# twopi-style radial layout in pure Rust and emits SVG
# directly, then `usvg::Tree::from_str` parses it and
# `resvg::render` rasterises against the tiny-skia Pixmap we
# encode to PNG. tiny-skia is pulled in transitively via
# typst-render, so no extra direct dep needed.
resvg = "0.47"
# typst-kit — convenience kit for tools that embed Typst. We enable:
# * `fonts` — fontdb-backed system-font searcher.
# * `embed-fonts` — bundle Computer Modern + Linux Libertine so
#   manuscripts work on hosts without system fonts (gated at
#   runtime by `typst_compile.bundle_fonts`; default on but
#   skippable). Adds ~10 MB.
# * `downloads` — `ureq`-based (no tokio) HTTP for `@preview/<pkg>`
#   resolution. Adds ~1 MB of TLS + http parsing.
# * `package` — package cache + `prepare_package` that gives us
#   on-disk paths for resolved imports. Gated at runtime by
#   `typst_compile.packages_enabled`; default on.
typst-kit = { version = "0.14", default-features = false, features = [
  "fonts",
  "embed-fonts",
  "downloads",
  "packages",
] }

# 1.2.8+ — embedded nushell as a library, powering the
# `Ctrl+Z o` floating shell pane.  We pull the minimum
# practical set: protocol types, the eval engine, the parser
# (also used for syntax highlight of the input line), and
# nu-command's default declarations so built-ins like `ls`,
# `where`, `str`, `path` are available.  No nu-cli (it owns
# reedline + the REPL, both of which conflict with ratatui).
# No nu-plugin (IPC; out of scope).  Versions pinned to 0.113
# — nu's API moves between minor releases, so we'll re-pin
# deliberately during dependency review.
nu-protocol = "0.112"
nu-engine   = "0.112"
nu-parser   = "0.112"
nu-command  = "0.112"
# Per-project shell command history (`.inkhaven/shell_history.db`).
# Bundled SQLite — no system dependency.  Tiny (~1 MB linked).
rusqlite = { version = "0.32", features = ["bundled"] }

# 1.2.20+ Phase G — cross-platform free-disk-space query for the
# low-disk pre-flight warning.  Tiny (a thin statvfs / GetDiskFreeSpace
# wrapper over libc/winapi); no system dependency.
fs2 = "0.4"
# LANG-1 P5.2 — UFO (Unified Font Object) reader/writer, pure Rust. The
# ConLang suite's `language font-build` compiles a directory of glyph SVGs
# (parsed by the in-tree usvg) into a UFO font source. First dependency added
# for the writing-systems/fonts pillar (deps approved).
norad = "0.18.4"
# LANG-1 P5.3 — in-process UFO → TTF compilation. write-fonts (fontations)
# assembles the OpenType tables directly; kurbo supplies the BezPath that
# write-fonts' `SimpleGlyph::from_bezpath` consumes (cubics auto-quadified).
# Both are ALREADY in the tree (write-fonts via typst-pdf→krilla→subsetter,
# kurbo via typst) at these exact versions — pinning them direct adds no new
# compiled crate, and is far lighter than fontc (which would drag in
# fontir/fontbe/fontdrasil + a duplicate write-fonts). NB norad's own `kurbo`
# feature targets kurbo 0.13, incompatible with write-fonts' 0.12 — so the
# norad-contour → BezPath bridge is hand-rolled against this 0.12.
write-fonts = "0.43"
kurbo = "0.12"

[build-dependencies]
# Compiles the in-tree Typst tree-sitter grammar (`src/grammar/`)
# during `cargo build`. See `build.rs`.
cc = "1"
# 1.2.10+ — parse `src/config.rs` at build time and
# emit a `(path, doc_comment)` lookup table for the
# config-TUI's in-process help pane.  Only the
# `parsing` feature is needed; we don't need
# `extra-traits` or `proc-macro` for our walk.
syn = { version = "2", features = ["full"] }

[dev-dependencies]
tempfile = "3"
# 1.3.32+ (road to 1.4.0) — property-based testing for the panic-prone parsers
# (calendar parse/format, HJSON config layering, language detection) + invariant
# checks. Pure-Rust, test-only; never linked into the shipped binary.
proptest = "1"
# LANG-1 P5.3 — parse-round-trip validation of the TTFs emitted by
# conlang::writing::compile. Already in the tree (via typst/krilla); test-only,
# never linked into the shipped binary.
skrifa = "0.37"
# 1.2.18+ I.1.2 — criterion bench harness for the
# perf-pass scenarios.  Subprocess-style benchmarks
# spawn the release binary against a generated fixture
# (`inkhaven gen-fixture`) and measure wall-clock time
# with statistical analysis.  html_reports feature
# emits HTML output for inspection.
criterion = { version = "0.5", default-features = false, features = ["html_reports"] }

# 1.2.18+ I.1.2 — bench entries.  Each is a standalone
# binary with `harness = false` (criterion provides its
# own main).  Run with `cargo bench --bench <name>`.
# Requires INKHAVEN_BENCH_FIXTURE=<path> to a project
# generated via `inkhaven gen-fixture`.  See
# `benches/README.md`.
[[bench]]
name = "startup"
harness = false

[[bench]]
name = "search"
harness = false

[profile.release]
lto = "thin"
codegen-units = 1
strip = "symbols"

# Single-crate workspace. Kept explicit so any future vendored-source
# experiments under `vendor/` don't accidentally become workspace
# members. The grammar itself lives in-tree at `src/grammar/`.
[workspace]
members = ["."]
