Harness: the preview pane says only "Hugo wird gestartet…" for the whole first build, and Hugo starts after the startup sync #362

Closed
opened 2026-11-01 01:07:13 +01:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Summary

The preview pane says nothing but "Starting Hugo…" for the whole first build,
which on a 2,573-page site is 2.5 minutes, so people reload a harness that is
working. Show what is happening — elapsed time, site size, Hugo's own output —
make the elapsed time survive a reload via the status API, and start Hugo
before the startup sync instead of after it.

Current behavior

  • PreviewPane's fallback branch (state neither running nor crashed)
    renders the preview.starting string and nothing else, although the pane
    already receives output and the status store already polls it.
  • GET /api/status carries no start time; the supervisor records none. A
    reload (on hosted: a re-poll of the wake) starts the user's clock at zero.
  • cmd/harness serve runs startupSync to completion before
    supervisor.Start, so in branch mode Hugo launches only after the sync's
    commit, fetch, reset, collapse and push (about 10 s on gbv-aktuell).

Confirmed by reading the code; the timings are from the hosted journal in the
original report below.

Desired behavior

The placeholder is a centered state, not a word. While the state is
starting the pane shows, centered:

  • a mark and the heading "Starting Hugo…" (the existing string);
  • the elapsed time since the current Hugo run started, ticking once a second,
    formatted for reading ("12 s", "2 min 05 s"), never negative;
  • the site's page count when the tree has loaded ("2,573 pages"), omitted
    until then;
  • once the elapsed time passes about ten seconds, one sentence that the first
    build of a site takes a while and the page stays usable afterwards (wording
    in both en.json and de.json);
  • below it, the tail of output in the same monospace block the crash panel
    uses, scrolled to the last line, so Hugo's "Start building sites" and any
    ERROR lines are visible while it works.

The same placeholder serves a restart after a crash (the state returns to
starting): the clock then counts that run.

The status API says when the run started. GET /api/status gains
startedAt: RFC3339, the time the current Hugo process was launched; ""
before the first launch. It is set per run, so a restart resets it, and it
stays set while running (its age is harmless there). The client computes
the elapsed time from it and clamps a negative difference (clock skew) to 0.

Hugo starts before the sync. In serve, the supervisor starts before
startupSync; the listener still comes after the sync, as today, so nothing
else about the startup order changes. A sync that moves the checkout is
picked up by Hugo's watcher as a change (see Decisions), so no restart logic
is needed.

Decisions

  • Launch Hugo first, unconditionally; keep listening after the sync. The
    report's condition ("only when the checkout is in sync") cannot be known
    before the sync runs, and it is not needed: Hugo 0.161.1 creates its file
    watcher before the first build (commands/server.go, Run: newWatcher
    then build), so a reset --hard during the build queues a rebuild rather
    than being lost — the same path a POST /api/git/sync on a running editor
    takes. The listener stays after the sync so the editor never serves a
    moving checkout. Accepted cost: a transient build error if Hugo reads a
    file mid-reset, cleared by the queued rebuild.
  • The hint line is elapsed-based, not size-based. Showing it after ~10 s
    avoids a "large site" threshold rule that nobody owns; the page count is
    shown as a fact whenever the tree is loaded.
  • The page count is the client's count of the tree it already holds.
    GET /api/status takes no lock and does no content or git work (ADR 0030)
    and must stay that way; the tree poll already delivers every node, and "a
    node with path === "" is synthesized, not a page" is an existing wire fact
    (docs/api.md GET /api/tree) the client already applies. Count the
    default-language tree's nodes with a non-empty path.
  • startedAt is a time, not a duration. It lets the hosted dashboard
    show the same clock later. Skew is handled by clamping.
  • Reuse, no new vocabulary. The placeholder is the styleguide's "Centered
    state" (.center, .center__box, .center__mark in components.css),
    whose description already names "starting"; the output tail reuses the
    crash panel's output block (one class for both). No new colour, token,
    component or styleguide sample.

Key interfaces

  • supervisor.Status — gains StartedAt time.Time, set under mu when
    runOnce launches the child; zero before the first launch. Starting hint:
    internal/supervisor/supervisor.go.
  • GET /api/status response hugo.startedAt: string — RFC3339 or "";
    statusResponse/hugoStatus in internal/server; HugoStatus and the
    store in web/src/api/status.ts and web/src/stores/hugoStatus.ts (one
    more per-field signal, so unchanged values do not notify).
  • PreviewPane props — receives the start time and a page count accessor
    (from the page tree store the module context already holds; the pane is
    mounted in web/src/modules.tsx).
  • cmd/harness serve — sup.Start moves ahead of startupSync; the
    ensureMediaRoot call must still precede sup.Start (Hugo mounts the
    media directory). Starting hint: cmd/harness/main.go, reconcile.go.
  • i18n: new keys in web/src/i18n/en.json and de.json (the parity test
    enforces both); the elapsed-time format and the {count} plural use the
    existing interpolation.

Acceptance criteria

  • With state starting, the preview pane shows the heading, a ticking
    elapsed time, the page count once the tree is loaded, and the tail of
    output in a scrolling monospace block.
  • The "takes a while" sentence is absent before ~10 s elapsed and present
    after, in English and German.
  • After a reload the elapsed time continues from the server's
    startedAt, not from zero; a startedAt in the future reads as 0 s.
  • GET /api/status returns startedAt as RFC3339 once Hugo was launched
    and "" before; a restart after a crash gives a newer value.
  • In branch mode, serve launches Hugo before the startup sync and
    listens after it; stderr still logs the sync result. In WIP mode
    nothing observable changes.
  • A sync that changes the checkout while Hugo builds ends in a rebuilt
    preview with no manual restart (integration test with a fake remote
    and a slow-building fixture, or a supervisor test that proves a file
    change during the first build is followed by "Change detected").
  • docs/api.md GET /api/status documents startedAt; docs/harness.md
    "Serving" and docs/architecture.md "Branch mode" state the new order.
  • No new colour literal, token or shared class; web/src/styles.css
    passes its styleguide test; the preview pane uses .center and the
    shared output block.

How to verify

  • go test ./... (supervisor, server route/doc tests, cmd/harness
    serve and reconcile tests, docs/ drift tests) and cd web && npm test
    (PreviewPane tests, i18n parity, styles test).
  • Manual: harness serve on the scaffold site and reload the editor during
    the first build on a large fixture (a copy of the scaffold with a few
    thousand generated pages) — the clock, the count and the output tail
    appear; the clock survives the reload.
  • Branch mode: a repo whose remote draft differs from the local checkout —
    Hugo logs "Start building sites" before the stderr "startup sync" line,
    then "Change detected" and a successful rebuild.

Assumptions to check

  • The pinned Hugo for this repo is 0.161.1 (watcher armed before the first
    build, commands/server.go Run). Confirm the pin and the ordering for
    the version the scaffold ships; if a version builds before watching, the
    harness must restart Hugo when SyncResult.Changed is true instead.
  • Hugo's incremental rebuild handles a reset touching many files and a
    changed config without a restart; a config change triggers Hugo's own
    full reload.
  • The page tree store exposes (or can cheaply expose) a node count to the
    module context the preview pane is rendered from.

Out of scope

  • deckle-hosted's dashboard or "preparing" page showing the clock (hosted
    #163); this issue only supplies startedAt.
  • Shortening the build itself or the sync's 10 s.
  • A spinner component in the styleguide; the mark suffices.
  • Changing the preview-site skill or AGENTS.md "Previewing locally": neither
    reads the new field, and the local startup order it describes (WIP mode)
    is unchanged.

Blocked by

None - can start immediately

Original report

What happens

On gbv-aktuell (2,573 pages) the first Hugo build takes 2.5 minutes in the hosted instance (deckle-hosted #163). For all of it PreviewPane shows the placeholder preview.starting and nothing else: no elapsed time, no hint that a large site takes minutes, none of Hugo's output although the supervisor holds its last 200 lines in Status.Output. Users read it as hung and reload, which on hosted re-polls the wake and changes nothing.

cmd/harness/main.go also runs startupSync to completion before sup.Start, so on this site Hugo starts 10 s after the container does. The sync's reset can move the checkout, so starting Hugo first would risk a second full build; but the sync's git work and Hugo's config and content read could overlap for the common in-sync case, or the supervisor could start with the build paused until the sync reports.

Fix

  • The placeholder shows elapsed time since state was first seen as starting, the site's page count (the tree poll has it), and a line such as "The first build of a site this size takes a few minutes"; below it the tail of output as the crash panel already shows it, so Hugo's own "Start building sites" and its error lines are visible while it works.
  • GET /api/status gains startedAt (when the current Hugo run started) so the elapsed time survives a reload and the hosted dashboard can show it too.
  • Start Hugo in parallel with the startup sync when the sync finds the checkout in sync with the remote (the usual case after a clean stop), and after it otherwise.

Evidence

Journal of deckle-instance-gbv-aktuell on 2026-10-31: deckle harness listening 18:37:44, site preview ready 18:40:07; the earlier start 15:50:17 to 15:55:37. web/src/components/PreviewPane.tsx Match when={true} branch; internal/supervisor Status.Output.

> *This was generated by AI during triage.* ## Summary The preview pane says nothing but "Starting Hugo…" for the whole first build, which on a 2,573-page site is 2.5 minutes, so people reload a harness that is working. Show what is happening — elapsed time, site size, Hugo's own output — make the elapsed time survive a reload via the status API, and start Hugo before the startup sync instead of after it. ## Current behavior - `PreviewPane`'s fallback branch (state neither `running` nor `crashed`) renders the `preview.starting` string and nothing else, although the pane already receives `output` and the status store already polls it. - `GET /api/status` carries no start time; the supervisor records none. A reload (on hosted: a re-poll of the wake) starts the user's clock at zero. - `cmd/harness serve` runs `startupSync` to completion before `supervisor.Start`, so in branch mode Hugo launches only after the sync's commit, fetch, reset, collapse and push (about 10 s on gbv-aktuell). Confirmed by reading the code; the timings are from the hosted journal in the original report below. ## Desired behavior **The placeholder is a centered state, not a word.** While the state is `starting` the pane shows, centered: - a mark and the heading "Starting Hugo…" (the existing string); - the elapsed time since the current Hugo run started, ticking once a second, formatted for reading ("12 s", "2 min 05 s"), never negative; - the site's page count when the tree has loaded ("2,573 pages"), omitted until then; - once the elapsed time passes about ten seconds, one sentence that the first build of a site takes a while and the page stays usable afterwards (wording in both `en.json` and `de.json`); - below it, the tail of `output` in the same monospace block the crash panel uses, scrolled to the last line, so Hugo's "Start building sites" and any ERROR lines are visible while it works. The same placeholder serves a restart after a crash (the state returns to `starting`): the clock then counts that run. **The status API says when the run started.** `GET /api/status` gains `startedAt`: RFC3339, the time the current Hugo process was launched; `""` before the first launch. It is set per run, so a restart resets it, and it stays set while `running` (its age is harmless there). The client computes the elapsed time from it and clamps a negative difference (clock skew) to 0. **Hugo starts before the sync.** In `serve`, the supervisor starts before `startupSync`; the listener still comes after the sync, as today, so nothing else about the startup order changes. A sync that moves the checkout is picked up by Hugo's watcher as a change (see Decisions), so no restart logic is needed. ## Decisions - **Launch Hugo first, unconditionally; keep listening after the sync.** The report's condition ("only when the checkout is in sync") cannot be known before the sync runs, and it is not needed: Hugo 0.161.1 creates its file watcher before the first build (`commands/server.go`, `Run`: `newWatcher` then `build`), so a `reset --hard` during the build queues a rebuild rather than being lost — the same path a `POST /api/git/sync` on a running editor takes. The listener stays after the sync so the editor never serves a moving checkout. Accepted cost: a transient build error if Hugo reads a file mid-reset, cleared by the queued rebuild. - **The hint line is elapsed-based, not size-based.** Showing it after ~10 s avoids a "large site" threshold rule that nobody owns; the page count is shown as a fact whenever the tree is loaded. - **The page count is the client's count of the tree it already holds.** `GET /api/status` takes no lock and does no content or git work (ADR 0030) and must stay that way; the tree poll already delivers every node, and "a node with `path === ""` is synthesized, not a page" is an existing wire fact (`docs/api.md` GET /api/tree) the client already applies. Count the default-language tree's nodes with a non-empty `path`. - **`startedAt` is a time, not a duration.** It lets the hosted dashboard show the same clock later. Skew is handled by clamping. - **Reuse, no new vocabulary.** The placeholder is the styleguide's "Centered state" (`.center`, `.center__box`, `.center__mark` in `components.css`), whose description already names "starting"; the output tail reuses the crash panel's output block (one class for both). No new colour, token, component or styleguide sample. ## Key interfaces - `supervisor.Status` — gains `StartedAt time.Time`, set under `mu` when `runOnce` launches the child; zero before the first launch. Starting hint: `internal/supervisor/supervisor.go`. - `GET /api/status` response `hugo.startedAt: string` — RFC3339 or `""`; `statusResponse`/`hugoStatus` in `internal/server`; `HugoStatus` and the store in `web/src/api/status.ts` and `web/src/stores/hugoStatus.ts` (one more per-field signal, so unchanged values do not notify). - `PreviewPane` props — receives the start time and a page count accessor (from the page tree store the module context already holds; the pane is mounted in `web/src/modules.tsx`). - `cmd/harness serve` — `sup.Start` moves ahead of `startupSync`; the `ensureMediaRoot` call must still precede `sup.Start` (Hugo mounts the media directory). Starting hint: `cmd/harness/main.go`, `reconcile.go`. - i18n: new keys in `web/src/i18n/en.json` and `de.json` (the parity test enforces both); the elapsed-time format and the `{count}` plural use the existing interpolation. ## Acceptance criteria - [ ] With state `starting`, the preview pane shows the heading, a ticking elapsed time, the page count once the tree is loaded, and the tail of `output` in a scrolling monospace block. - [ ] The "takes a while" sentence is absent before ~10 s elapsed and present after, in English and German. - [ ] After a reload the elapsed time continues from the server's `startedAt`, not from zero; a `startedAt` in the future reads as 0 s. - [ ] `GET /api/status` returns `startedAt` as RFC3339 once Hugo was launched and `""` before; a restart after a crash gives a newer value. - [ ] In branch mode, `serve` launches Hugo before the startup sync and listens after it; stderr still logs the sync result. In WIP mode nothing observable changes. - [ ] A sync that changes the checkout while Hugo builds ends in a rebuilt preview with no manual restart (integration test with a fake remote and a slow-building fixture, or a supervisor test that proves a file change during the first build is followed by "Change detected"). - [ ] `docs/api.md` GET /api/status documents `startedAt`; `docs/harness.md` "Serving" and `docs/architecture.md` "Branch mode" state the new order. - [ ] No new colour literal, token or shared class; `web/src/styles.css` passes its styleguide test; the preview pane uses `.center` and the shared output block. ## How to verify - `go test ./...` (supervisor, server route/doc tests, `cmd/harness` serve and reconcile tests, `docs/` drift tests) and `cd web && npm test` (PreviewPane tests, i18n parity, styles test). - Manual: `harness serve` on the scaffold site and reload the editor during the first build on a large fixture (a copy of the scaffold with a few thousand generated pages) — the clock, the count and the output tail appear; the clock survives the reload. - Branch mode: a repo whose remote draft differs from the local checkout — Hugo logs "Start building sites" before the stderr "startup sync" line, then "Change detected" and a successful rebuild. ## Assumptions to check - The pinned Hugo for this repo is 0.161.1 (watcher armed before the first build, `commands/server.go` `Run`). Confirm the pin and the ordering for the version the scaffold ships; if a version builds before watching, the harness must restart Hugo when `SyncResult.Changed` is true instead. - Hugo's incremental rebuild handles a reset touching many files and a changed config without a restart; a config change triggers Hugo's own full reload. - The page tree store exposes (or can cheaply expose) a node count to the module context the preview pane is rendered from. ## Out of scope - deckle-hosted's dashboard or "preparing" page showing the clock (hosted #163); this issue only supplies `startedAt`. - Shortening the build itself or the sync's 10 s. - A spinner component in the styleguide; the mark suffices. - Changing the preview-site skill or AGENTS.md "Previewing locally": neither reads the new field, and the local startup order it describes (WIP mode) is unchanged. ## Blocked by None - can start immediately ## Original report ### What happens On gbv-aktuell (2,573 pages) the first Hugo build takes 2.5 minutes in the hosted instance (deckle-hosted #163). For all of it `PreviewPane` shows the placeholder `preview.starting` and nothing else: no elapsed time, no hint that a large site takes minutes, none of Hugo's output although the supervisor holds its last 200 lines in `Status.Output`. Users read it as hung and reload, which on hosted re-polls the wake and changes nothing. `cmd/harness/main.go` also runs `startupSync` to completion before `sup.Start`, so on this site Hugo starts 10 s after the container does. The sync's reset can move the checkout, so starting Hugo first would risk a second full build; but the sync's git work and Hugo's config and content read could overlap for the common in-sync case, or the supervisor could start with the build paused until the sync reports. ### Fix - The placeholder shows elapsed time since `state` was first seen as `starting`, the site's page count (the tree poll has it), and a line such as "The first build of a site this size takes a few minutes"; below it the tail of `output` as the crash panel already shows it, so Hugo's own "Start building sites" and its error lines are visible while it works. - `GET /api/status` gains `startedAt` (when the current Hugo run started) so the elapsed time survives a reload and the hosted dashboard can show it too. - Start Hugo in parallel with the startup sync when the sync finds the checkout in sync with the remote (the usual case after a clean stop), and after it otherwise. ### Evidence Journal of `deckle-instance-gbv-aktuell` on 2026-10-31: `deckle harness listening` 18:37:44, `site preview ready` 18:40:07; the earlier start 15:50:17 to 15:55:37. `web/src/components/PreviewPane.tsx` `Match when={true}` branch; `internal/supervisor` `Status.Output`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle#362
No description provided.