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 countplan_json: page plan (used to compute total pages)error_message: displayed onerror/abortedlast_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
3000msinstatus.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 includescloning,planning,generating_pages, andrendering, 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 siteDocumentation 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 to100%and label toComplete. - If
plan_jsonis unavailable, count showsN pagesonly; denominator and percentage are unknown.
Warning:
page_countreflects 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: truewhen 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
errorwithout a user-triggered abort or explicit generation exception in the live UI.