Status and Progress Monitoring

The status page (/status/{name}/{provider}/{model}) is the per-variant monitoring view for doc generation. It combines backend state from the projects table with client-side polling and UI reconstruction (progress bar + activity log).

Status Model and Data Source

Status values are defined centrally and stored in the projects row for each variant.

# src/docsfy/storage.py
VALID_STATUSES = frozenset({"generating", "ready", "error", "aborted"})

The status page fetches variant state from the variant API endpoint:

# src/docsfy/main.py
@app.get("/api/projects/{name}/{provider}/{model}")
async def get_variant_details(
    request: Request,
    name: str,
    provider: str,
    model: str,
) -> dict[str, str | int | None]:
    name = _validate_project_name(name)
    project = await _resolve_project(
        request, name, ai_provider=provider, ai_model=model
    )

    return project

Important fields used by the page:

  • status: high-level state (generating, ready, error, aborted)
  • current_stage: pipeline stage (cloning, planning, etc.)
  • page_count: generated/cached page count
  • plan_json: page plan (used to compute total pages)
  • error_message: displayed on error/aborted
  • last_commit_sha, last_generated: metadata updated on completion

Polling Behavior

The status page uses interval polling (not WebSockets), with overlap protection and auth-aware redirect handling.

// src/docsfy/templates/status.html
var POLL_INTERVAL_MS = 3000;

function startPolling() {
    if (pollTimer) return;
    pollTimer = setInterval(pollProject, POLL_INTERVAL_MS);
}

var _polling = false;
function pollProject() {
    if (_polling) return;
    _polling = true;
    fetch('/api/projects/' + encodeURIComponent(PROJECT_NAME) + '/' + encodeURIComponent(PROJECT_PROVIDER) + '/' + encodeURIComponent(PROJECT_MODEL), { credentials: 'same-origin', redirect: 'manual' })
        .then(function(res) {
            if (isAuthRedirect(res)) { handleAuthRedirect(); stopPolling(); return null; }
            if (!res.ok) throw new Error('Not found');
            return res.json();
        })
        .then(function(proj) {
            if (!proj) return;
            updateFromProject(proj);
        })
        .catch(function() {
            /* Silently fail; retry on next interval */
        })
        .finally(function() { _polling = false; });
}

Note: Polling interval is hardcoded to 3000ms in status.html; there is no environment variable for this.

Polling stops when status becomes terminal (ready, error, aborted) or when auth expires.

Stage Updates (Backend Lifecycle)

The backend writes stage transitions via update_project_status(...) as generation progresses:

# src/docsfy/main.py
await update_project_status(
    project_name,
    ai_provider,
    ai_model,
    status="generating",
    owner=owner,
    current_stage="cloning",
)

await update_project_status(
    project_name,
    ai_provider,
    ai_model,
    status="generating",
    owner=owner,
    current_stage="planning",
)

await update_project_status(
    project_name,
    ai_provider,
    ai_model,
    status="generating",
    owner=owner,
    current_stage="generating_pages",
    plan_json=json.dumps(plan),
)

await update_project_status(
    project_name,
    ai_provider,
    ai_model,
    status="generating",
    owner=owner,
    current_stage="rendering",
    page_count=len(pages),
)

await update_project_status(
    project_name,
    ai_provider,
    ai_model,
    status="ready",
    owner=owner,
    current_stage=None,
    last_commit_sha=commit_sha,
    page_count=page_count,
    plan_json=json.dumps(plan),
)

Up-to-date shortcut (no regeneration) is represented as status="ready" + current_stage="up_to_date".

# src/docsfy/main.py
if old_sha == commit_sha:
    await update_project_status(
        project_name,
        ai_provider,
        ai_model,
        status="ready",
        owner=owner,
        current_stage="up_to_date",
    )
    return

Warning: Backend can emit current_stage="incremental_planning", but the status page stage order only includes cloning, planning, generating_pages, and rendering, so that phase is shown generically.

Activity Log Semantics

The activity log is reconstructed client-side from status, current_stage, page_count, and plan_json. It is not a server-side event stream.

// src/docsfy/templates/status.html
var ICON_MAP = {
    done: 'icon-check',
    active: 'icon-spinner-sm',
    error: 'icon-x-circle',
    pending: 'icon-circle'
};

var STAGES = ['cloning', 'planning', 'generating_pages', 'rendering'];

Behavior:

  • On initial load and stage transitions: buildInitialLog() clears and rebuilds entries.
  • On page count increase: the last active "Generating..." entry is converted to "Generated...", then next active page entry is appended.
  • On completion (ready): log finalizes with:
  • Rendered documentation site
  • Documentation ready!
  • On up_to_date: log is replaced with a single entry:
  • Repository unchanged, docs already up to date
  • On error/aborted: active entry is marked as error and terminal failure entry is appended.

Progress Bar Semantics

The status page uses page_count as numerator and total_pages_from_plan as denominator when available.

// src/docsfy/templates/status.html
if (totalPagesFromPlan > 0) {
    var pct = Math.min(Math.round((newPageCount / totalPagesFromPlan) * 100), 100);
    progressBar.style.width = pct + '%';
    progressCount.textContent = newPageCount + ' / ' + totalPagesFromPlan + ' pages';
} else {
    progressCount.textContent = newPageCount + ' pages';
}

page_count is updated during page generation from cache file count:

# src/docsfy/generator.py
existing_pages = len(list(cache_dir.glob("*.md")))
await update_project_status(
    project_name,
    ai_provider,
    ai_model,
    owner=owner,
    status="generating",
    page_count=existing_pages,
)

And forced regenerations reset count to zero:

# src/docsfy/main.py
if force:
    await update_project_status(
        project_name,
        ai_provider,
        ai_model,
        status="generating",
        owner=owner,
        page_count=0,
    )

Progress completion behavior:

  • On ready, UI forces progress bar to 100% and label to Complete.
  • If plan_json is unavailable, count shows N pages only; denominator and percentage are unknown.

Warning: page_count reflects files present in the page cache, not strictly "new pages generated in this exact run." Incremental/cached runs can appear to jump.

Tip: Use force: true when you want a fresh 0→N progress curve for reruns.

Abort and Failure Monitoring

Abort action from the status page calls the variant-specific abort endpoint:

  • POST /api/projects/{name}/{provider}/{model}/abort

On successful abort, backend writes:

  • status="aborted"
  • error_message="Generation aborted by user"
  • current_stage=None

The status page then:

  • stops polling
  • switches log status to Aborted
  • shows regenerate controls inline (provider/model/force + Regenerate)

If the server restarts mid-generation, startup logic converts orphaned generating projects to error:

# src/docsfy/storage.py
cursor = await db.execute(
    "UPDATE projects SET status = 'error', error_message = 'Server restarted during generation', current_stage = NULL WHERE status = 'generating'"
)

Note: This restart recovery is why a variant can move to error without a user-triggered abort or explicit generation exception in the live UI.