${locks.length ? `${locks.length} lock${locks.length === 1 ? "" : "s"}` : "No Git locks"}${activeProcesses.length ? `${activeProcesses.length} active Git process(es)` : "No matching active Git process detected"}
${locks.length ? `
${locks.map((lock) => `
${escapeHtml(lock.name)}${Math.round(lock.ageMs / 1000)}s old · ${escapeHtml(lock.modifiedAt)}
`).join("")}
` : ""}${recommendations.length ? `
${recommendations.map((item) => `
${escapeHtml(item.label)}${item.safe ? "Safe automated action" : item.action ? "Creates a safety branch before changing history" : "Review required"}
${item.action ? `` : ""}
`).join("")}
` : ""}`
: '
Scan before repairing. ForgeFlow checks every .lock file in the actual Git directory, not only index.lock.
';
const syncState = status.counts?.changed
? `${status.counts.changed} local file${status.counts.changed === 1 ? "" : "s"} need protection`
: status.branch?.ahead || status.branch?.behind
? `${status.branch.ahead || 0} ahead · ${status.branch.behind || 0} behind`
: "Preview against Gitea before changing files";
return `
Branches
${branchRows}
Stashes
${stashRows}
Gitea workspace sync
Make tracked files match the current upstream branch exactly
${escapeHtml(syncState)}
Safe mirror, never silent overwrite
ForgeFlow fetches Gitea, previews additions, changes and deletions, then protects local Codex work before resetting. Local commits go to a recovery branch; modified and untracked files go to a stash.
${icon("shield")}Ignored runtime data such as .env, dependency folders and local databases is preserved. Background awareness only fetches; it never applies this sync automatically.
${escapeHtml(status.branch?.head || "No branch")} → ${escapeHtml(status.branch?.upstream || "No upstream")}
${icon("shield")}Origin repair changes only the Git remote URL. Git health scans the actual Git directory, repairs only proven stale lock files and never changes source files or commits.
`;
}
function renderGitValidator(repository) {
const report = ui.gitValidation;
if (!report)
return `
${projectIllustration("diagnostics")}
Repository assurance
Validate Git best practices
Inspect repository identity, branch governance, tracked secrets, file hygiene and safe local synchronization settings.
${server.error ? "No inventory could be read until the SSH connection works." : "Docker returned no containers, Compose projects or DockerMan templates."}
ForgeFlow will query Docker directly. A failed connection is shown explicitly instead of being reported as zero deployments.
`
: `
No Unraid server configured
Add the server with password authentication and ForgeFlow can copy and deploy projects directly.
`;
return `
Server inventory
Live Docker, Compose and DockerMan discovery, linked to Gitea
${servers.length ? `
${serverCards}
` : empty}
${icon("shield")}Server pull fetches an exact Gitea commit through a repository-scoped read-only deploy key, validates Compose and only then promotes the release. Direct copy remains an explicit fallback.
Discover live Unraid workloads, verify them against Gitea and release an exact commit through a protected server pull.
${projectIllustration("deploy")}
${missingDockerMan.length ? `` : ""}
${active.length ? `
${icon("pulse")} ${active.length} deployment operation${active.length === 1 ? " is" : "s are"} still active. ForgeFlow reconciles these against the live server automatically.
` : ""}${renderServerInventory()}
Linked deployment environments
Stable Compose identity, live container health and exact Gitea commit parity
`;
}
const HELP_TOPICS = [
{
id: "getting-started",
icon: "rocket",
category: "Basics",
title: "Start with a repository",
summary: "Connect Gitea, discover local projects and understand the Local → Gitea → Server flow.",
keywords: "setup connect token roots clone local remote overview",
steps: [
"Open Settings and validate the Gitea URL and token.",
"Add the parent folders that contain your projects, then save and rescan.",
"Select a repository. The release rail shows local changes, Gitea parity and the linked server release.",
"Use Changes to review work; use Git tools for branches, synchronization and pull requests.",
],
note: "ForgeFlow does not modify a project merely because you opened it or refreshed the overview.",
},
{
id: "workspace-sync",
icon: "refresh",
category: "Git & Gitea",
title: "Make a local project match Gitea",
summary: "Clean tracked leftovers without losing local Codex work or ignored runtime data.",
keywords: "sync mirror pull reset deleted files cleanup stash recovery codex upstream dirty",
steps: [
"Open the repository, choose Git tools and select Preview Gitea sync.",
"Review every file that will be added, changed or removed.",
"Choose Protect local work & synchronize only when the preview matches your intention.",
"ForgeFlow creates a recovery branch for local commits and a stash for modified or untracked files before matching the upstream commit.",
],
note: "Ignored files such as .env, local databases and dependency folders remain untouched. Background awareness only fetches metadata and never applies a sync.",
},
{
id: "changes",
icon: "file",
category: "Git & Gitea",
title: "Review, commit and publish changes",
summary: "Keep intentional local changes separate from remote updates.",
keywords: "changes stage hunk commit push branch pull request conflict",
steps: [
"Review selected files or individual hunks in Changes.",
"Enter a clear commit message and commit the reviewed selection.",
"Fetch before publishing; if Gitea changed, synchronize or resolve divergence first.",
"Push directly only when branch protection permits it, otherwise create a pull request.",
],
note: "The action panel explains the current blocker and only enables operations that are safe for the selected state.",
},
{
id: "deployment-linking",
icon: "link",
category: "Deployments",
title: "Link server workloads to repositories",
summary: "Turn Docker, Compose and DockerMan evidence into one explicit repository deployment.",
keywords: "server inventory docker compose dockerman container detect linked review reconcile",
steps: [
"Open Deployments and scan the configured server.",
"Review proposed matches. ForgeFlow uses Compose paths, Git provenance, labels and container metadata; names alone are not trusted.",
"Confirm Review & link for a correct candidate, or choose the repository manually.",
"Use Review reconciliation whenever a saved link conflicts with current server evidence.",
],
note: "External and system containers remain monitoring-only until you explicitly link them. A linked workload also appears on the matching repository.",
},
{
id: "deployments",
icon: "deploy",
category: "Deployments",
title: "Deploy an exact Gitea commit",
summary: "Validate, pull, build and promote a release without damaging the live service.",
keywords: "deploy release unraid preflight rollback health exact sha commit server pull",
steps: [
"Open the repository Deployments tab and select the intended environment.",
"Run preflight and repair blocking configuration before deployment.",
"Deploy only the shown target commit. Server pull fetches that exact commit instead of an ambiguous latest branch state.",
"Follow the run stages and health result. Failed promotion keeps or restores the previous release where supported.",
],
note: "Commit parity means Local, Gitea and Server identify the same revision; a running container alone does not prove a correct release.",
},
{
id: "deploy-keys",
icon: "key",
category: "Deployments",
title: "Repair repository deploy keys",
summary: "Give the server read-only access to exactly the repository it must pull.",
keywords: "ssh key deploy key gitea permission server pull fingerprint authentication",
steps: [
"Run deployment preflight for the environment.",
"Use the offered deploy-key repair when repository access is missing.",
"ForgeFlow creates or reuses a repository-scoped key, registers the public key read-only in Gitea and verifies access from the server.",
"Re-run preflight and deploy only after the server can resolve and fetch the target commit.",
],
note: "The private key remains on the configured server. ForgeFlow does not copy your personal Gitea token into deployment commands.",
},
{
id: "git-validator",
icon: "shield",
category: "Quality",
title: "Use Git Validator safely",
summary: "Find repository hygiene issues and apply only reviewed repairs.",
keywords: "validator hygiene gitignore readme license branch protection secrets large files fix repair",
steps: [
"Open a repository and choose Git Validator.",
"Select the policy that fits the repository and run a fresh scan.",
"Open each finding to understand its evidence and recommended repair.",
"Preview repairable findings before applying them, then rescan to verify the result.",
],
note: "A score is guidance, not proof of correctness. Repairs that can alter repository policy or files always require an explicit action.",
},
{
id: "updates-diagnostics",
icon: "update",
category: "Maintenance",
title: "Update or troubleshoot ForgeFlow",
summary: "Install signed releases and collect useful diagnostics without exposing credentials.",
keywords: "update installer signed release diagnostics logs support error updater restart",
steps: [
"Open Settings and choose Check now under ForgeFlow updates.",
"Download the signed packaged release, then choose Apply & restart.",
"If an operation fails, open Diagnostics and run the troubleshooter.",
"Export a diagnostic bundle when deeper inspection is needed; tokens, passwords and private keys are redacted.",
],
note: "Binary auto-update is available only from a packaged installation. Source checkouts continue to use the normal development workflow.",
},
];
function renderHelp() {
const query = String(ui.helpQuery || "").trim().toLowerCase();
const topics = HELP_TOPICS.filter((topic) =>
!query || `${topic.category} ${topic.title} ${topic.summary} ${topic.keywords} ${topic.steps.join(" ")} ${topic.note}`.toLowerCase().includes(query),
);
const categories = [...new Set(HELP_TOPICS.map((topic) => topic.category))];
const topicMarkup = topics.map((topic) => {
const isOpen = topic.id === ui.helpTopic || Boolean(query);
return `${icon(topic.icon)}${escapeHtml(topic.category)}${escapeHtml(topic.title)}${escapeHtml(topic.summary)}${icon("chevron", "help-chevron")}
${topic.steps.map((step) => `
${escapeHtml(step)}
`).join("")}
${icon("shield")}${escapeHtml(topic.note)}
`;
}).join("");
return `
ForgeFlow guide
How can we help?
Clear, practical instructions for repositories, Gitea synchronization, server deployments and maintenance.
${projectIllustration("flow")}
${query ? `Search results` : "Everything you need to operate ForgeFlow"}
Connections, project discovery, secure SSH servers and application updates.
Gitea connection
${state.gitea.hasToken ? `Connected as ${escapeHtml(state.gitea.user?.login || "user")}` : "Not connected"}
${escapeHtml(state.gitea.baseUrl || "No Gitea instance configured")}
ForgeFlow updates
Signed packaged updates from ${escapeHtml(state.updates?.owner || "Jens")}/${escapeHtml(state.updates?.repo || "ForgeFlow")}
${icon(update?.available ? "download" : "check")}${update ? (update.available ? `ForgeFlow ${escapeHtml(update.remoteVersion)} is available${update.packaged ? "" : " in the source repository"}` : `ForgeFlow ${escapeHtml(update.currentVersion)} is up to date`) : `Current version ${escapeHtml(ui.boot.appVersion)}`}${update ? `Branch ${escapeHtml(update.branch)} · commit ${escapeHtml(update.shortSha)} · checked ${formatDate(update.checkedAt)}` : "No update check in this session."}
${icon("shield")}${update && !update.packaged ? "Source checkouts must be updated with Git after reviewing the exact commit. Integrated source replacement remains disabled until source archives are publisher-signed." : "Packaged updates require an Ed25519 publisher signature that binds the exact commit, artifact name, size and SHA-256 digest."}
SSH / Unraid servers
Credentials are encrypted locally; a new host fingerprint is shown before authentication.
No SSH server configured. Add your Unraid server before creating an SSH deployment profile.
'}
Git remote maintenance
Standardize linked repositories to the current Gitea SSH URLs.
This replaces legacy aliases and renamed owners only after an explicit click. Local commits and files are not changed.
Project roots
The first folder is the default clone destination. ForgeFlow automatically creates one subfolder per repository.
${state.workspaceRoots.map((root, index) => `
${index === 0 ? 'Default' : ""}
`).join("")}
Background awareness
Read-only remote awareness. Use 0 to disable; fetching never changes project files.
${icon("shield")}Remote awareness only fetches branch metadata. ForgeFlow never resets, cleans or overwrites a workspace in the background.
Desktop integration
Separate arguments with |. Placeholders: {path}, {file}, {line}
Encrypted configuration backup
Repository mappings, servers, deployment profiles and preferences are encrypted. Tokens, passwords, passphrases and operation history are never exported.
Appearance
Danger zone
Reset removes local ForgeFlow configuration, repository links, profiles and operation history. It does not modify Git repositories or Gitea.
`;
}
function preflightTone(status) {
return status === "pass"
? "success"
: status === "fail"
? "danger"
: status === "warning"
? "warning"
: "";
}
function renderPreflightChecks(
report,
emptyMessage = "Run the preflight to verify this configuration.",
) {
if (!report?.checks?.length)
return `
ForgeFlow records structured development diagnostics locally while removing tokens, passwords, authorization headers, private keys and user-home paths.
${icon("shield")}Credentials are never added to the diagnostic bundle. Known runtime secrets are redacted again during export. You can inspect the ZIP before sharing it.
${escapeHtml(logs || "Waiting for operation output…")}
${operation.failure ? `
${icon("error")}
${escapeHtml(operation.failure.stage)}
${escapeHtml(operation.failure.message)}
` : ""}
`;
}
function renderStatusbar() {
const state = ui.boot?.state;
const repository = selectedRepository();
const active = operations().filter(
(operation) => !isTerminalOperation(operation.status),
).length;
return ``;
}
function renderSetup() {
const steps = ["Readiness", "Gitea", "Folders", "Discovery", "Ready"];
let body = "";
if (ui.setupStep === 0) {
body = `
Check this computer
ForgeFlow verifies Git, writable storage and protected credential support before you enter any connection details.
${icon("shield")}Your Gitea token is entered only inside this local desktop application. It is never included in diagnostic logs or support bundles.
${renderPreflightChecks(ui.systemPreflight, "Run the readiness check to verify this computer.")}
Available even before Gitea is connected.
`;
} else if (ui.setupStep === 1) {
body = `
Connect your Gitea instance
Enter the URL and a personal access token created on your own Gitea server. ForgeFlow validates it locally and stores it using operating-system encryption when available.
${icon("shield")}Use the narrowest permissions that allow repository reads and Actions workflow dispatch. The setup guide explains this without requiring you to share the token.
`}
`;
} else if (ui.setupStep === 2) {
body = `
Select development folders
Choose parent folders. ForgeFlow discovers Git working trees below them and matches their origin to Gitea.