US09-04: Write the User Manual with Generated Screenshots (#110)
Some checks failed
Test / suites (push) Failing after 2m56s
Test / container (push) Failing after 5m27s

This commit was merged in pull request #110.
This commit is contained in:
2026-08-23 23:37:31 +02:00
parent 282e8b51e6
commit f1442527a2
34 changed files with 997 additions and 37 deletions

View File

@@ -3,7 +3,7 @@
// HTML. Mutating actions are disabled while a job holds the library_write lock and
// say why (concept §shared interaction rules: read-only browsing stays available).
import { api } from "./api.js";
import { el, errorBanner, setActiveNav } from "./dom.js";
import { el, errorBanner, setActiveNav, show } from "./dom.js";
import { navigate } from "./router.js";
import { subscribeJob } from "./events.js";
@@ -26,7 +26,7 @@ export async function renderWorkflow(root) {
try {
data = await api.workflow();
} catch (error) {
root.replaceChildren(errorBanner(`Failed to load workflow: ${error.message}`));
show(root, errorBanner(`Failed to load workflow: ${error.message}`));
return;
}
const active = data.active_job;
@@ -67,7 +67,7 @@ export async function renderWorkflow(root) {
)
);
});
root.replaceChildren(
show(root,
el("h1", {}, "Workflow"),
jobBanner(active),
el("div", { class: "stepper" }, ...cards)
@@ -105,7 +105,7 @@ export async function renderSafety(root, params) {
try {
data = await api.safetyQueue({ state });
} catch (error) {
root.replaceChildren(errorBanner(`Failed to load safety queue: ${error.message}`));
show(root, errorBanner(`Failed to load safety queue: ${error.message}`));
return;
}
@@ -149,7 +149,7 @@ export async function renderSafety(root, params) {
)
);
root.replaceChildren(
show(root,
el("h1", {}, "Safety review"),
tabs,
el(
@@ -197,7 +197,7 @@ export async function renderLibrary(root, params) {
try {
data = await api.libraryAssets({ q, offset, limit, sort: params.sort || "path" });
} catch (error) {
root.replaceChildren(errorBanner(`Failed to load library: ${error.message}`));
show(root, errorBanner(`Failed to load library: ${error.message}`));
return;
}
@@ -220,7 +220,7 @@ export async function renderLibrary(root, params) {
)
);
root.replaceChildren(
show(root,
el("h1", {}, "Library"),
el("div", { class: "toolbar" }, search, el("span", { class: "muted", "data-testid": "library-total" }, `${data.total} photos`)),
data.rows.length ? el("div", { class: "cluster-grid" }, ...cards) : el("p", { class: "muted" }, "No matching photos."),
@@ -235,7 +235,7 @@ export async function renderAnalyze(root) {
try {
[counts, workflow] = await Promise.all([api.analysisCounts(), api.workflow()]);
} catch (error) {
root.replaceChildren(errorBanner(`Failed to load analysis: ${error.message}`));
show(root, errorBanner(`Failed to load analysis: ${error.message}`));
return;
}
const active = workflow.active_job;
@@ -269,7 +269,7 @@ export async function renderAnalyze(root) {
"Analyze eligible SFW assets"
);
root.replaceChildren(
show(root,
el("h1", {}, "Analyze"),
jobBanner(active),
el(
@@ -292,10 +292,10 @@ export async function renderStats(root) {
try {
data = await api.libraryStats();
} catch (error) {
root.replaceChildren(errorBanner(`Failed to load stats: ${error.message}`));
show(root, errorBanner(`Failed to load stats: ${error.message}`));
return;
}
root.replaceChildren(
show(root,
el("h1", {}, "Stats"),
el("div", { class: "counts-grid", "data-testid": "stats-status" }, ...Object.entries(data.status).map(([k, v]) => stat(k, v))),
facetBlock("Top tags", data.top_tags),
@@ -338,7 +338,7 @@ export async function renderAlbums(root, params = {}) {
api.renameRecovery(),
]);
} catch (error) {
root.replaceChildren(errorBanner(`Failed to load albums: ${error.message}`));
show(root, errorBanner(`Failed to load albums: ${error.message}`));
return;
}
@@ -385,7 +385,7 @@ export async function renderAlbums(root, params = {}) {
)
: el("p", { class: "muted" }, "No albums with evidence yet.");
root.replaceChildren(
show(root,
el("h1", {}, "Albums"),
renameBlocked
? el(