draftline 0.1.5

Git-native versioning for creative content workflows.
Documentation
# Draftline

Git-native versioning for creative content workflows.

Draftline is a Rust library for apps that need safe version history for folders full of creative content. It exposes business-friendly concepts like workspaces, versions, variations, change sets, preflight reports, and recovery state while keeping Git as a storage implementation detail.

## Content policy

Use a `ContentPolicy` to describe which workspace files are user content and which files are app/runtime state. Paths are workspace-relative, extensions are normalized case-insensitively, and `.draftline` state is excluded by default.

```rust
use draftline::{ContentPolicy, Workspace};

fn main() -> Result<(), draftline::DraftlineError> {
    let policy = ContentPolicy::new()
        .include_paths(["content", "assets"])?
        .include_extensions(["md", "txt"])?
        .exclude_paths(["content/private"])?;

    let workspace = Workspace::init_with_policy("my-content", policy)?;
    Ok(())
}
```

## Variation metadata

Variations have stable Draftline names backed by Git refs. Hosts can attach display metadata without changing those names.

```rust
use draftline::{VariationMetadata, Workspace};

fn main() -> Result<(), draftline::DraftlineError> {
    let workspace = Workspace::init("my-content")?;
    let version = workspace.save_version("Initial draft")?;
    let variation = workspace.create_variation_from_with_metadata(
        version.id(),
        "draft-a",
        VariationMetadata::new()
            .with_label("Draft A")
            .with_slug("draft-a"),
    )?;

    assert_eq!(variation.display_label(), "Draft A");
    Ok(())
}
```

`label` is user-facing display text. `slug` is host-owned metadata for URLs, routing, or integration. Neither field changes the underlying variation name or Git branch.

## Opaque ID round-trips

Draftline IDs are opaque. Prefer using typed IDs in host DTOs and letting `serde` serialize them as strings across IPC, HTTP, or CLI boundaries. Deserializing a `VersionId` validates that the value is Draftline's full canonical ID form.

```rust
use draftline::{VariationId, VersionId};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct PreviewCommand {
    version: VersionId,
    variation: VariationId,
}

let command: PreviewCommand = serde_json::from_str(
    r#"{
        "version": "0123456789abcdef0123456789abcdef01234567",
        "variation": "draft-a"
    }"#,
)?;
assert_eq!(command.version.as_str(), "0123456789abcdef0123456789abcdef01234567");
# Ok::<(), Box<dyn std::error::Error>>(())
```

`VersionId::from_canonical_string` is a lower-level helper for boundaries that cannot use typed serde DTOs. It validates Draftline's canonical ID shape, not whether the version exists in a workspace. `VersionId` reconstruction accepts only Draftline's full canonical version ID form, rejecting abbreviated prefixes and host-defined IDs. `VariationId` remains infallible to reconstruct for branch-name compatibility, but applications should still treat it as opaque; workspace operations validate whether a variation exists or is meaningful.

## Remote credentials

Remote operations accept credential callbacks so host apps can fetch, clone, and publish through their own authentication flow.

```rust,no_run
use draftline::{RemoteCredential, RemoteOptions, Workspace};

fn main() -> Result<(), draftline::DraftlineError> {
    let token = std::env::var("GITHUB_TOKEN").unwrap();
    let mut options = RemoteOptions::new().with_credentials(move |request| {
        if request.allows_username_password {
            Ok(RemoteCredential::UsernamePassword {
                username: "x-access-token".to_string(),
                password: token.clone(),
            })
        } else {
            Ok(RemoteCredential::Default)
        }
    });

    let workspace = Workspace::open("my-content")?;
    workspace.fetch_remote_with_options("origin", &mut options)?;
    Ok(())
}
```