feat(editor): header slot for host navigation, compact preview status when hosted #141
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!141
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/139"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #139
What this does
Gives the editor's header a slot for host navigation. A host that reverse-proxies the editor answers
GET /api/hoston the editor's origin, and the editor renders the host's brand, breadcrumb segments with switcher menus, sections and account menu in its own header. With a host present the Hugo status becomes a compact dot with a short label.Standalone deckle is unchanged. With no host the header is pixel-for-pixel what it is on
main.The contract
deckle owns it, and the README section Host navigation (after "Serving the preview behind a reverse proxy") is the document a host builds against. It covers the path, the version 1 shape with a field reference, every validation rule, how the header renders the data, link rules, the compact status, safety and versioning.
The proposed shape from the issue is kept. These points were refined while implementing, and all are documented:
hrefmust start withhttp://orhttps://and parse as a URL. An entry whosehrefis present but not acceptable is dropped. An entry with nohrefis shown as plain text.{"version": 1}counts as a host being present. The header switches to its hosted form and shows whichever parts were sent.How it is built
web/src/api/host.ts: the sanitized types, the sanitizer and the fetch. It never throws.web/src/stores/hostNav.ts: holds the data in a Solid store and merges refreshes in place, so an open menu survives a refresh.web/src/publish.ts: anonFinalhook on the feedback store. Page publish and Publish all both trigger the refresh through it.web/src/components/HostNav.tsx: brand, segments, sections, account, marks, and one shared disclosure for the menus.web/src/components/StatusPill.tsx: acompactvariant. Without it the DOM is exactly as before.web/src/App.tsx: the wiring, and the guard for host links.web/src/leave.ts: the one place the editor navigates the tab itself, as a seam for tests.internal/server: a comment andTestHostNavigationPathIsReserved. The harness gained no handler, flag or option.Acceptance criteria
TestHostNavigationPathIsReserved;HostHeader.test.tsx"no host"; header screenshots identical tomainin ChromiumHostHeader.test.tsx"valid host data"hrefis not a linkHostNav.test.tsx,HostHeader.test.tsx"segments and sections"aria-currentasserted in both test filesHostNav.test.tsxfilter, closing and keyboard blocks; keyboard-only run in ChromiumHostHeader.test.tsxtable of eight failures;host.test.tshrefdropped; bad color falls back; markup shown literallyhost.test.ts;HostHeader.test.tsx"hostile data"HostHeader.test.tsx"unsaved changes"; verified in Chromium, including that no native prompt appears after DiscardHostHeader.test.tsx"refresh";publish.test.tsonFinalStatusPill.test.tsx;HostHeader.test.tsx"compact status"README.mdVerification
npm test: 55 files, 1,556 tests pass.tsc --noEmitis clean.npm run buildsucceeds.go vet ./...andgo test ./...pass.main: the standalone header has identical HTML and identical pixels in all three Hugo states, with and without a publish chip, at 1280 and 800.Chromium is the only browser that was tested.
Decisions a reviewer should know about
/api/hostanswers, then switches in place. Hiding the header until then would have changed standalone's first paint./api/hostonce per load and once after each publish. That follows from the contract.okis a dot,busya spinning ring,errora diamond,lockeda lock,nonea hollow ring, each with a text alternative. The mockup drew all butlockedas dots, which differ by color only.PublishChipnow wraps its message in a span with the full text astitle. Standalone pixels are unchanged. In the hosted bar a long message shortens with an ellipsis so the breadcrumb keeps its room.contain: inline-size, and the chip's shortening on:has().scaffold/site/AGENTS.mdis untouched. Nothing it documents changed: no command, flag, sidecar schema or scaffold layout.Out of scope, as the issue says
No knowledge of organizations, sites, roles or any host product in deckle's code. No harness flags. No change to publishing behavior, the module rail, the tree or any module.
Found along the way
#140: in the media module, Escape in a dialog also closes the details panel behind it. It exists on
mainand is not changed here.🤖 Generated with Claude Code
[autoland] verdict: pass
Verdict: PASS — validated against #139, awaiting the maintainer's explicit confirmation before merge.
ci / build-and-test (pull_request)is green (frontend build and vitest,go vet,go test,go build). It was not re-run. CI has no type-check step, sotsc --noEmitwas run separately on the head (c90b868): clean.Closes #139present.scaffold/site/AGENTS.mdis rightly untouched: no command, flag, schema or scaffold layout changed.main(9fb6f57).web/src/api/host.ts,stores/hostNav.ts,components/HostNav.tsx,StatusPill.tsx,PublishChip.tsx,App.tsx,publish.ts,leave.ts,api/client.ts,styles.css,internal/server, README). The sanitizer accepts only absolute http(s) hrefs, six-digit hex colors and the enumerated statuses, and host text is rendered as text. The standalone path changes only by one extraGET /api/host, a span around the publish chip message, and additive CSS scoped to the hosted header. The diff matches the issue's contract and stays in scope.Notes, none blocking:
:has()andcontain: inline-size./api/hostper load and per finished publish. That follows from the contract in the issue.