Put the developer docs under docs/dev and index the folder for users first

docs/ had 26 developer documents flat beside the manual, and the two
audiences are very differently sized: most readers want the manual and
the gesture reference, a few want the register, the designs and the
measurements. The manual and gestures.md stay at the top; everything for
someone changing the code moves to docs/dev/, and the two documents that
name their own successors — the v0.1 milestone and the UI-refinement plan
— go to docs/dev/archive/ rather than being deleted, since both are still
cited. docs/README.md is the index, users first.

Every reference follows: code comments, Cargo manifests, the workflows,
the pre-commit hook, the bench and traceability tools (which locate the
repo root by docs/dev/requirements.md now), packaging, the Docker READMEs,
CLAUDE.md, CONTRIBUTING.md and the README. The matrix links one level
deeper and is regenerated. Links out of the moved documents into the tree
gain a level; a link checker over every Markdown file finds none broken.
This commit is contained in:
2026-09-20 16:20:15 +02:00
parent 08727cff5a
commit 6b1aac477d
137 changed files with 658 additions and 572 deletions
+2 -2
View File
@@ -47,10 +47,10 @@ dr-lens.workspace = true
dr-pipeline.workspace = true
dr-catalog.workspace = true
# The face pipeline, with the ONNX runtime: this is the layer that actually
# runs the models over the library (docs/faces.md).
# runs the models over the library (docs/dev/faces.md).
dr-face = { workspace = true, features = ["inference"] }
# The engine behind both. `native` here means the *app* may look for a
# runtime file; the build stays C-free either way (docs/inference.md §3).
# runtime file; the build stays C-free either way (docs/dev/inference.md §3).
dr-inference-engine = { workspace = true, features = ["native"] }
dr-thumbs.workspace = true
# The library module writes scan results straight into the catalog, so it
+1 -1
View File
@@ -14,7 +14,7 @@
//!
//! # What it measures, and what it cannot
//!
//! docs/faces.md §12 M4 is recall against hand-labelled faces. There are no
//! docs/dev/faces.md §12 M4 is recall against hand-labelled faces. There are no
//! labels here, so this is the cheaper question that decides whether M4 is
//! worth the labelling: *do the detectors disagree, where, and does the
//! disagreement look like faces*. A candidate whose extras are all real faces
+2 -2
View File
@@ -8,7 +8,7 @@
//!
//! # Why this exists beside the button in the Identity screen
//!
//! Indexing a real library is hours of work (docs/faces.md §12.2), and the
//! Indexing a real library is hours of work (docs/dev/faces.md §12.2), and the
//! cases where that is worth starting — an overnight pass, a fresh import, a
//! machine left running — are exactly the ones where holding a window open is
//! the wrong shape. The check half is useful on its own: it is cheap, it
@@ -181,7 +181,7 @@ fn main() {
\n\
Face crops must be sampled from a buffer longer than {}px and the\n\
store's largest tier is {}px, so every image here would be refused.\n\
The measurement behind that floor is docs/faces.md §7b.\n\
The measurement behind that floor is docs/dev/faces.md §7b.\n\
\n\
Index from the app's Identity screen instead: that pass renders at\n\
native resolution, which is what the crop needs.",
+1 -1
View File
@@ -15,7 +15,7 @@
//!
//! `--export DIR` also writes each native render out as a JPEG, so the
//! model-free tooling in `dr-face`'s examples — `eyes` above all — can be
//! run over native pixels rather than proxies (docs/faces.md §17.4).
//! run over native pixels rather than proxies (docs/dev/faces.md §17.4).
//!
//! The models must have had their input dims frozen first; see
//! `tools/fix-face-model-shapes.sh`.
+1 -1
View File
@@ -26,7 +26,7 @@
//! not: a burst is a property of a *run* of frames, so a per-image job would
//! regroup the library once per photograph. Since the two have to happen in that
//! order and the second cannot be split, both live in one pass — the same
//! argument docs/catalog.md §10.2 makes for face clustering.
//! argument docs/dev/catalog.md §10.2 makes for face clustering.
//!
//! # It is never on the UI thread
//!
+3 -3
View File
@@ -258,7 +258,7 @@ impl SegmentationJob {
///
/// The model reads the photograph as captured, not as edited: the
/// segmentation must survive an exposure change, or every slider would
/// invalidate the masks that depend on it (docs/segmentation.md §3).
/// invalidate the masks that depend on it (docs/dev/segmentation.md §3).
///
/// **Still the sensor's orientation, deliberately.** Standing the picture
/// up is what `segmentation::compute` does to the buffer this returns,
@@ -2387,7 +2387,7 @@ impl DevelopSession {
}
// ----------------------------------------------------------------------
// Segmentation (S15, docs/segmentation.md)
// Segmentation (S15, docs/dev/segmentation.md)
// ----------------------------------------------------------------------
/// This session's name, carried by any work started against it.
@@ -3569,7 +3569,7 @@ impl DevelopSession {
/// The instance's own coverage is the mask, rather than the watershed
/// regions it overlaps. Snapping to regions was the original design and
/// it is not currently worth doing: the hierarchy those ids index into
/// collapses on a photograph (docs/segmentation.md §15), so snapping
/// collapses on a photograph (docs/dev/segmentation.md §15), so snapping
/// would trade the model's approximately-right outline for a
/// confidently-wrong one.
pub fn add_subject_mask(&mut self, index: usize) -> Option<String> {
+3 -3
View File
@@ -12,7 +12,7 @@
//! thing that makes the feature affordable: a library that has been browsed has
//! already paid for its proxies, so face indexing adds **no RAW decodes that
//! were not already happening**. The tier is `ThumbSize::Large` — 1024 on the
//! long edge — and docs/faces.md §7 has the table of what the embedder actually
//! long edge — and docs/dev/faces.md §7 has the table of what the embedder actually
//! receives at that resolution.
//!
//! # Why clustering is a separate pass and not a job
@@ -39,7 +39,7 @@ use dr_types::ImageId;
/// The tier a stored face crop is cut from. See the module note.
///
/// **No longer a tier faces are found or cropped at**, and the distinction is
/// the whole of `docs/faces.md` §7: cutting an already-located face out of a
/// the whole of `docs/dev/faces.md` §7: cutting an already-located face out of a
/// stored 1024px proxy so the People screen can draw a thumbnail of it is
/// fine, because that crop is only ever looked at. Sampling the *embedder's*
/// 112×112 from a buffer this size is what left 47% of the reference library's
@@ -845,7 +845,7 @@ pub fn spawn_store_face_sweep(
// Models first: they are the expensive failure, and there is no point
// listing ten thousand images before discovering the weights are
// missing. This is also the path a library with face indexing enabled
// but no model downloaded takes (docs/faces.md §2.2), so it must be a
// but no model downloaded takes (docs/dev/faces.md §2.2), so it must be a
// quiet return rather than an error.
let mut detector = match Detector::from_path(&models.detector) {
Ok(d) => d,
+1 -1
View File
@@ -1,4 +1,4 @@
//! The app's side of `dr-inference-engine` (docs/inference.md §8).
//! The app's side of `dr-inference-engine` (docs/dev/inference.md §8).
//!
//! What lives here is what only the app knows: where the runtime file might
//! be, where the disposable cache goes, which model files this device has,
+1 -1
View File
@@ -1485,7 +1485,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
.map(std::rc::Rc::new)
},
// The weights are not shipped and are not a build input
// (docs/faces.md §2): the user puts them beside the catalog,
// (docs/dev/faces.md §2): the user puts them beside the catalog,
// and their absence is the ordinary state of a fresh install.
{
let lib = library.clone();
+2 -2
View File
@@ -4261,7 +4261,7 @@ pub fn thumbs_dir(account: &Account) -> PathBuf {
/// **Not shipped with the application** and not a build input: the InsightFace
/// weights carry a non-commercial research grant incompatible with this
/// project's licence, so the user obtains them and the app loads them from here
/// (docs/faces.md §2). An absent directory is the ordinary state of a fresh
/// (docs/dev/faces.md §2). An absent directory is the ordinary state of a fresh
/// install, not an error.
pub fn face_models_dir(account: &Account) -> PathBuf {
catalog_path(account)
@@ -4334,7 +4334,7 @@ impl FaceModelPaths {
}
/// Where the inference engine keeps what it derives per device: the probe
/// result and compiled engines (docs/inference.md §4, §5). A peer of
/// result and compiled engines (docs/dev/inference.md §4, §5). A peer of
/// `thumbs`, not of the catalog: disposable, regenerable, never synced.
pub fn inference_cache_dir() -> PathBuf {
data_root().join("inference")
+1 -1
View File
@@ -22,7 +22,7 @@
//! the catalog holds has authoritative backing outside it, which is what makes
//! a rebuild survivable — but a manual collection is a set of images the user
//! assembled by hand and nothing in the filesystem records it
//! (`docs/catalog.md` §8.1). So the labels say which one loses them, and the
//! (`docs/dev/catalog.md` §8.1). So the labels say which one loses them, and the
//! rebuild is not given the affirmative styling while a restore is on offer.
//!
//! # Why the scan is held back
+1 -1
View File
@@ -12,7 +12,7 @@
//! Everything above this module works through `&dyn RemoteBackend`, and every
//! account it opens is a [`dr_sync::Account`] — configuration with no server
//! in it. Adding a backend is implementing two traits and adding a line to
//! [`registry`]; nothing else in `dr-ui` changes. See `docs/storage.md`.
//! [`registry`]; nothing else in `dr-ui` changes. See `docs/dev/storage.md`.
//!
//! # Why the registry is built here and not in `dr-sync`
//!
+1 -1
View File
@@ -322,7 +322,7 @@ pub fn registry(
// detector found on a proxy as found. The re-index converges on
// provenance: every box, landmark, crop and vector from the current
// detector over the native render, because those are what every
// later per-face pass reads (docs/faces.md §17.4a).
// later per-face pass reads (docs/dev/faces.md §17.4a).
out.push(Repair {
name: "face-detection",
label: "images to detect faces in with the chosen detector",
+2 -2
View File
@@ -6,7 +6,7 @@
//! # What used to be here
//!
//! A watershed over-segmented the image, a merge tree turned that into a
//! granularity ladder, and a click walked up it (docs/segmentation.md, arms A
//! granularity ladder, and a click walked up it (docs/dev/segmentation.md, arms A
//! and C). It is gone from this path, and the reason is measured rather than
//! aesthetic: on a real photograph the saddles are near zero almost
//! everywhere, so the merge order joins everything meaningful before it joins
@@ -354,7 +354,7 @@ pub struct Options {
/// Run the model over overlapping tiles instead of the whole frame once.
///
/// Off by default and deliberately so. The graph's input is fixed at
/// 640x640 (docs/segmentation.md F6), so every image is letterboxed into
/// 640x640 (docs/dev/segmentation.md F6), so every image is letterboxed into
/// it and a subject 200px across in a 1600px proxy reaches the model at
/// 80px — which is where a coarse outline comes from. Tiling is the only
/// route to more resolution with a fixed window, and it costs one
+1 -1
View File
@@ -2,7 +2,7 @@
//! Standard XMP sidecars, read into the catalog and written back out of it.
//!
//! `dr-xmp` reads and writes the file. This is the other half the requirement
//! asks for and the half `docs/outstanding.md` called "the larger": which
//! asks for and the half `docs/dev/outstanding.md` called "the larger": which
//! images a given `.xmp` describes, what the catalog holds about them, how
//! the two are reconciled, where a disagreement goes, and the write in the
//! other direction. Nothing here parses XML and nothing in `dr-xmp` knows a
+1 -1
View File
@@ -276,7 +276,7 @@ export component IdentityScreen inherits Rectangle {
/// existed. The same sweep, fetching the same originals; the button says
/// what it will actually do.
in property <bool> coverage-read-only: false;
// No model on disk: face indexing cannot run at all (docs/faces.md §2.2).
// No model on disk: face indexing cannot run at all (docs/dev/faces.md §2.2).
in property <bool> model-missing: false;
in property <int> picked-count: 0;
/// Where leaving goes back to — "‹ Library" or "‹ Develop".
+1 -1
View File
@@ -23,7 +23,7 @@
//
// A restore keeps the user's collections; a rebuild cannot, because a manual
// collection is a set of images the user assembled by hand and nothing in the
// filesystem records it (docs/catalog.md §8.1). So the two answers are not
// filesystem records it (docs/dev/catalog.md §8.1). So the two answers are not
// interchangeable, the difference is stated in the button rather than in a
// second dialogue after it, and the rebuild is the plain button even when it
// is the only one available.
+2 -2
View File
@@ -98,7 +98,7 @@ export component SettingsPage inherits Rectangle {
/// it — so both buttons go quiet together.
callback reindex-faces();
/// TRACES: FR-CULL-8
/// Which detector the pass finds faces with — docs/faces.md §12.3 for
/// Which detector the pass finds faces with — docs/dev/faces.md §12.3 for
/// what each costs and finds. The choice is a model change: coverage is
/// counted per pipeline, so picking another one starts from nothing.
in property <[string]> face-detector-labels;
@@ -144,7 +144,7 @@ export component SettingsPage inherits Rectangle {
in property <string> adapter;
in property <string> backend;
/// What runs the neural models and how it was chosen — the two lines
/// `dr_ui::inference::about_lines` produces (docs/inference.md §4).
/// `dr_ui::inference::about_lines` produces (docs/dev/inference.md §4).
in property <string> inference-backend;
in property <string> inference-detail;
in property <int> fps;
+1 -1
View File
@@ -17,7 +17,7 @@
// answer the same question and only one of them is on screen at a time.
//
// **Why a rail is the right shape for a finger and the wrong one for a mouse.**
// See `groups-in-rail` below, and D-N6 in `docs/ui-navigation.md` for the
// See `groups-in-rail` below, and D-N6 in `docs/dev/ui-navigation.md` for the
// decision it reverses and the half of that decision that still stands.
//
// **Why it left the chip strip.** These four used to be chips at the top of