Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RFD-0030 — ox doc --document-deps and external-package documentation

Draft Opened 2026-05-05 · Status: Draft

Question

How does ox doc document a workspace whose dependencies resolve outside the workspace tree — registry, git, or path-deps — and how does the resulting site link out to those packages’ documentation?

Context

RFD-0029 ratified ox doc as a thin renderer over a stable JSON IR (oxc-doc::Site). RFD-0029 §3.2 + Phase 4 ship the React + Vite SSG that consumes that IR and emits the static HTML site. What neither RFD addressed is the cross-package navigation problem when the deps aren’t local.

Concretely, today (2026-05-05):

  • A workspace whose members are all path-deps inside the workspace tree (e.g. examples/lease-story with all 7 packages under packages/) renders correctly. Cross-package qname links resolve because the consumer site can find every dep’s Site.item_pages in the bundle envelope.
  • A typical project — single-[project] ox.toml declaring cofris = "0.2.2" from the registry — also “renders”: every concept page, every relation, every rule. But cross-package qname links 404. The renderer synthesizes URLs of the form cofris/0.2.2/cofris/concept.JournalEntry.html against the same target/doc/ tree, but cofris lives at ~/.argon/packages/cofris/0.2.2/ (the cache), not under target/doc/. Links break silently.
  • Workspaces mixing path-deps and registry-deps work for the path side and 404 for the registry side. The new external-deps row in the workspace dep graph (RFD-0030 prep, landed 2026-05-05) at least makes the externals visible, but they have no click target.

The user-facing surface needs to handle the registry case as the default — it’s how most projects will be structured once Argon’s package registry is populated.

Cargo’s design is the reference. cargo doc documents the workspace + its transitive dependencies by default; --no-deps opts out. The output tree at target/doc/ is self-contained. Argon should match this UX: ox doc produces a docs tree that works whether or not the user has internet access, with deps’ source coming from the local cache.

Decision

Adopt Cargo’s mental model with three concrete commitments:

  1. ox doc --document-deps builds docs for transitive intra-workspace + cached registry/git/path deps, producing a self-contained target/doc/ tree. Default-off behaviour matches the user’s current expectation. The flag flips when docs.argon.dev ships and --no-deps becomes the new default — at that point externals get rewritten to https://docs.argon.dev/<pkg>/<ver>/ URLs by the renderer.

  2. Dep docs come from the local resolver cache at ~/.argon/packages/<pkg>/<ver>/. The cache is already populated by ox install and content-hash verified against ox.lock. ox doc --document-deps walks the lockfile’s transitive closure, opens each dep’s manifest, builds its Site against the cached source, and includes it in the bundle envelope passed to argon-doc-ui. No network. No registry-side cooperation required.

  3. The IR doesn’t change. BundleEnvelope.sites: Vec<Site> already accepts arbitrary members; --document-deps just feeds it more. The workspace dep graph’s “external” row collapses to zero when every external dep is locally documented (the IR builder demotes externals into full WorkspaceMember-equivalents at envelope-construction time).

Mechanics

$ ox doc --document-deps          # documents workspace + every transitive dep
$ ox doc                          # default: workspace only (today's behaviour)
$ ox doc --no-deps                # explicit opt-out (post-docs.argon.dev default)

The implementation seam is argon/ox/src/main.rs::run_doc_workspace (and run_doc_single). After collecting the workspace’s own member sites, when --document-deps is set:

  1. Read ox.lock to get the transitive closure of every member’s deps with their pinned versions.
  2. For each external dep (source = registry / git / non-workspace path), locate the cached source: ~/.argon/packages/<pkg>/<ver>/. Verify content-hash against the lockfile entry; refuse divergence (same rule as ox audit).
  3. Build each dep’s Site via the same build_doc_for_project path used for workspace members. Each dep is treated as a single-package project for elaboration purposes — its own dep-resolution closure is what the lockfile already pinned, no fresh resolution needed.
  4. Append every external Site to BundleEnvelope.sites, mark them with is_external_dep: true on Site so the renderer can apply distinct breadcrumb/sidebar treatment (“From cofris 0.2.2 (registry)” subtitle on the package index, slightly muted card on the workspace landing).
  5. The bundle envelope is invoked once as today — the SSG just iterates more sites.

Lockfile interaction

The lockfile is the source of truth for which dep versions get documented. ox doc --document-deps against a workspace with ufo = "0.2.4" in ox.lock documents ufo@0.2.4 from the cache. If ox.lock is missing, --document-deps errors out with a pointer to ox install (consistent with ox audit).

Caching strategy

Per-dep doc rendering is content-addressable. The output of building cofris@0.2.2’s Site from ~/.argon/packages/cofris/0.2.2/ depends only on:

  • The dep’s source content-hash (already in ox.lock).
  • The IR schema version (oxc-doc::DOC_IR_SCHEMA_VERSION, currently 1.1.0).
  • The set of doc extensions in scope from packages the dep transitively depends on.

The natural cache path is ~/.argon/cache/doc/<source-hash>-<schema>.json carrying the serialized Site. ox doc --document-deps checks this cache before re-elaborating; cache hits skip the entire elaborate pipeline for that dep. Cache invalidation is automatic — content-hash and schema bumps both produce new keys.

This is a substantial perf win for the common case: a workspace’s deps don’t change between ox doc runs, so re-rendering only touches the workspace’s own changed members.

URL synthesis

Three regimes:

ModeExternal-dep qname URL synthesis
ox doc (default)Cross-package links to deps render as styled text with no href (current Phase 4 behaviour for unresolvable qnames).
ox doc --document-depsCross-package links resolve to relative paths in the same target/doc/ tree (../../<pkg>/<ver>/<pkg>/concept.X.html).
ox doc post-docs.argon.devCross-package links resolve to https://docs.argon.dev/<pkg>/<ver>/<pkg>/concept.X.html. The renderer reads the registry’s base URL from a workspace-level [doc] config or a built-in default.

The third regime is preserved as the eventual default once docs.argon.dev exists. Until then, --document-deps is the way to get clickable cross-package links.

Workspace dep graph

WorkspaceSite.external_dependencies carries the externals today. Under --document-deps, the IR builder demotes each documented external into a full WorkspaceMemberSummary (with depth = 0 since externals are by definition foundations from the workspace’s perspective; sub-depth ranking among externals reads the dep graph and is computed inside the document-deps walk). The “external” row collapses; the foundation tier expands. Visually: the dep graph shows everything as first-class.

Site.is_external_dep flag

Pages from external deps are rendered identically to workspace members but marked so the renderer can:

  • Add a “Dependency” subtitle on the package index (“Documented from ~/.argon/packages/cofris/0.2.2/”).
  • Disable doc-test execution (ox test --doc runs only on workspace-owned items per RFD-0029).
  • Skip the “View source on <host>” affordance once that lands.

The flag is a single optional field on Site; doesn’t change the wire shape for workspace-only renders.

Non-goals

  • Network fetching during ox doc. Deps must already be in the local cache. ox install is the network-touching command; ox doc reads from disk only. (Future: ox doc --document-deps --auto-install could implicitly invoke ox install first, but that’s a UX-layer convenience over this RFD’s mechanics.)
  • Per-dep doc-extension scoping. [package.docs.panel] declarations in dep manifests already scope by transitive workspace dep closure (RFD-0029 Phase 4 fix). --document-deps doesn’t change this rule — a dep’s own manifest panels apply to its own pages, governed by the same closure rule.
  • Cross-version doc rendering (e.g. documenting ufo@0.2.3 and ufo@0.2.4 simultaneously). The lockfile pins one version per package; that’s what gets documented. Multi-version is a cargo doc non-feature too.
  • Registry-side doc hosting. docs.argon.dev is out of scope here. This RFD specifies the local-side URL synthesis once that host exists; the host’s own architecture (build-on-publish, on-demand build, etc.) is a separate problem.

Open questions

  1. Should --document-deps be the default before docs.argon.dev ships? Cargo defaults to documenting deps; we currently don’t. Defaulting to --document-deps produces a working docs tree out of the box but slows down ox doc substantially (factor of N for an N-dep workspace, mitigated by the doc-cache). My current bias: default off until docs are fast enough that the perf hit is invisible, then flip. Open for discussion.

  2. Lockfile-less --document-deps. A workspace member may run ox doc --document-deps from a checked-out source tree that happens to have ox.lock present but never resolved (e.g., fresh clone, ox install not yet run). Hard error or silently skip externals? Current bias: hard error pointing to ox install, matching ox audit’s posture.

  3. Doc-cache eviction policy. Content-hashed entries never collide, but the cache grows monotonically. Evict by LRU once ~/.argon/cache/doc/ exceeds N MB? Track via size + atime, periodic compaction in ox install? Defer for now — wait for the cache to actually grow before solving.

  4. Path-dep externals semantics. A workspace member declaring cofris = { path = "../shared/cofris" } has a path-dep external. Should --document-deps document it? Cargo’s behaviour: yes, but the path becomes a stability hazard (a path-dep at a different version than the workspace’s other consumers). My bias: yes, with a stern warning when the documented version diverges from any sibling member’s resolved version.

  5. is_external_dep flag granularity. Single boolean is enough for the v0 surface, but future work (semver-check, doc-coverage, dep-graph analysis) may want richer metadata: source-kind, content-hash, lockfile-pinned version. Bias: ship the boolean now; widen to a struct (Option<ExternalSource>) when a second consumer needs it.

Implementation plan

PhaseWorkStatus
0External-dep visibility in workspace dep graph (the “external” row, no click target).Landed 2026-05-05
1RFD ratified + lockfile-walking helper in ox::resolve that yields the cached source path + content-hash per transitive dep.This RFD
2--document-deps flag plumbed through ox docrun_doc_workspace + run_doc_single. Per-dep build_doc_for_project invocation against the cached source.Follow-up commit
3Doc-cache at ~/.argon/cache/doc/<hash>-<schema>.json. Skip elaborate when hit.Follow-up commit
4Site.is_external_dep: bool IR field + renderer-side breadcrumb / subtitle treatment.Follow-up commit
5URL-synthesis switch for the docs.argon.dev regime — workspace [doc] config field + renderer’s qname resolver respects it.When docs.argon.dev exists
6Default flip: ox doc becomes --document-deps (or docs.argon.dev-rewriting) by default; --no-deps opts out.Coordinated with docs.argon.dev launch

Connections

  • Builds on RFD-0029 — Doc comments and ox doc. The IR contract there is the load-bearing artifact this RFD extends.
  • Coordinates with the future docs.argon.dev registry-doc host (separate effort).
  • Touches ox.lock content-hash invariants — see RFD-0024 — Diagnostic codes for the OE3xxx package-loader code namespace; new errors here register under that scheme.