# 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.

## 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(())
}
```

## Tauri Workbench contract

The `draftline::tauri_contract` module exposes dependency-free command adapter
functions for Workbench and other Tauri hosts. Hosts can wrap these functions with
`#[tauri::command]` while keeping Draftline's Rust APIs as the source of truth
for preflight, execution, verification, and serializable error shapes.

The contract includes read-only diagnostics (`inspect_workspace`,
`verify_workspace`, `list_variations`, `list_support_refs`), selected-file
mutations (`selected_save`, `selected_shelve`, `selected_discard`), and remote
collaboration commands (`fetch_remote`, `preflight_apply_incoming`,
`apply_incoming`, `preflight_merge_incoming`, `merge_incoming`,
`merge_incoming_with_resolutions`, `publish_current_variation`). Collaboration
commands refresh remote-tracking state before reporting preflight results so host
UIs can render current `SyncState` values and then execute through Draftline's
tokenized apply, merge, and publish paths.

Conflicted merge preflight returns conflicts plus a token when the workspace and
remote heads are safe to merge, while `can_merge_cleanly` remains `false`. Hosts
should collect explicit user choices and call `merge_incoming_with_resolutions`
with the preflight token and one `MergeConflictResolution` per conflict. The
token binds execution to the local, remote, and merge-base commits the user
reviewed, so stale resolution submissions fail instead of resolving unseen
remote content. Whole-file choices support `use_ours`, `use_theirs`, `use_base`,
`delete`, or `use_content`; semantic `field_path` conflicts currently require a
host-produced `use_content` result for the resolved file.

The Workbench contract keeps credential handling out of frontend DTOs. Hosts route
commands through `DraftlineCommandContext` to configure content policy,
host-provided contributor attribution, backend-only remote credentials, and
redaction-safe workspace events in one place. Clone, open, adopt, fetch, publish,
apply, and merge command adapters all use the context so product frontends never
need to pass clone or fetch tokens over IPC.

```rust,no_run
use draftline::tauri_contract::{inspect_workspace, WorkspaceRequest};

#[tauri::command]
fn inspect_workspace_command(
    workspace_path: std::path::PathBuf,
) -> draftline::tauri_contract::TauriCommandResult<
    draftline::tauri_contract::WorkspaceDiagnostics,
> {
    draftline::tauri_contract::into_tauri_result(inspect_workspace(WorkspaceRequest {
        workspace_path,
    }))
}
```

For product hosts, prefer context-aware wrappers:

```rust,no_run
use draftline::{
    tauri_contract::{selected_save_with_context, DraftlineCommandContext, SelectedSaveRequest},
    ContentPolicy, Contributor, ContributorProfile,
};

let policy = ContentPolicy::new()
    .include("content")?
    .exclude(".chats")?
    .exclude("runtime")?;
let profile = ContributorProfile::new(
    Contributor {
        name: "Product User".to_string(),
        email: Some("user@example.invalid".to_string()),
    },
    Contributor {
        name: "Draftline Service".to_string(),
        email: Some("service@example.invalid".to_string()),
    },
);
let mut context = DraftlineCommandContext::new()
    .with_content_policy(policy)
    .with_contributor_profile(profile)
    .with_event_sink(|event| {
        // Tauri hosts can emit this as `draftline://workspace_event`.
        let _ = event.sequence;
    });
# let request = SelectedSaveRequest {
#     workspace_path: std::path::PathBuf::from("my-content"),
#     paths: vec![std::path::PathBuf::from("content/post.md")],
#     label: "Save".to_string(),
# };
let _ = selected_save_with_context(&mut context, request);
# Ok::<(), draftline::DraftlineError>(())
```

Treat `ContributorProfile` as product identity and service attribution, not as a
Git configuration screen. The host should derive `author` from the signed-in
product user, `saved_by` from the actor that initiated the save, and optional
`service` from the backend automation performing the operation. Draftline writes
normal Git author/committer metadata from that profile, but users should not need
to understand or edit `user.name` / `user.email` for product saves:

```rust,no_run
use draftline::{Contributor, ContributorProfile};

let profile = ContributorProfile::new(
    Contributor {
        name: "Avery Writer".to_string(),
        email: Some("avery@example.invalid".to_string()),
    },
    Contributor {
        name: "CutReady Sync Service".to_string(),
        email: Some("sync@example.invalid".to_string()),
    },
)
.with_service(Contributor {
    name: "CutReady".to_string(),
    email: None,
});
# let _ = profile;
```
