diff --git a/site/astro.config.mjs b/site/astro.config.mjs
index 6bb18dd4..e19f6cf1 100644
--- a/site/astro.config.mjs
+++ b/site/astro.config.mjs
@@ -46,7 +46,6 @@ export default defineConfig({
Footer: './src/components/starlight/Footer.astro',
MobileMenuFooter: './src/components/starlight/MobileMenuFooter.astro',
PageSidebar: './src/components/starlight/PageSidebar.astro',
- MarkdownContent: './src/components/starlight/MarkdownContent.astro',
},
// Split from a single 4,965-line starlight.css into ordered per-pass
// files (see docs/ARCHITECTURE.md). Order matters: several later
diff --git a/site/src/components/starlight/MarkdownContent.astro b/site/src/components/starlight/MarkdownContent.astro
deleted file mode 100644
index ad3b92d3..00000000
--- a/site/src/components/starlight/MarkdownContent.astro
+++ /dev/null
@@ -1,61 +0,0 @@
----
-/**
- * Article content, with the table of contents above it.
- *
- * Starlight puts the ToC in a fixed right-hand rail (`TwoColumnContent.astro`)
- * at >=72rem, and collapses it to a dropdown below that. This override keeps
- * the dropdown for narrow screens and moves the wide-screen version into the
- * content column, directly under the hero band.
- *
- * Which of the two shows is decided in styles/starlight/30-toc-under-hero.css,
- * at the same 50rem breakpoint the left sidebar uses -- not by Starlight's
- * 72rem utility classes. Keyed to 72rem, the 50-72rem band showed the
- * collapsed dropdown next to a fully expanded sidebar, which read as the ToC
- * having collapsed for no reason.
- *
- * It hangs off MarkdownContent rather than PageTitle: PageTitle renders
- * inside the hero's own panel, so the list ended up overlapping the title
- * inside the gradient band. This slot is the first thing in the content
- * column below it.
- *
- * The `.right-sidebar-panel` class is kept deliberately even though this is
- * no longer the right sidebar -- every rail, marker, hover and theme rule in
- * the 29-file override stack is keyed on it, and re-homing all of them is the
- * kind of change that stack makes risky. It now reads as a component name;
- * `docs-toc-under-hero` carries the layout.
- */
-import TableOfContents from 'virtual:starlight/components/TableOfContents';
-
-const { toc } = Astro.locals.starlightRoute;
----
-
-{
- toc && (
-
- )
-}
-
-
-
-
diff --git a/site/src/components/starlight/PageSidebar.astro b/site/src/components/starlight/PageSidebar.astro
index e6f247f5..08481d13 100644
--- a/site/src/components/starlight/PageSidebar.astro
+++ b/site/src/components/starlight/PageSidebar.astro
@@ -1,5 +1,6 @@
---
import MobileTableOfContents from 'virtual:starlight/components/MobileTableOfContents';
+import TableOfContents from 'virtual:starlight/components/TableOfContents';
const { sidebar: routeSidebar, toc } = Astro.locals.starlightRoute;
const sidebar = routeSidebar as SidebarEntry[];
@@ -104,9 +105,17 @@ const currentGroupLabel = formatCrumb(currentGroup?.label) || 'Start';
{
toc && (
<>
- {/* Narrow screens keep the collapsed dropdown. The wide-screen list
- moved to PageTitle.astro so it sits under the hero rather than in
- the right-hand rail. */}
+ {/* Wide screens: the contents list in the right-hand rail, which is
+ where Starlight puts it and where a reader looks for it. It was
+ briefly moved under the hero; that came from feedback about the
+ NARROW list not collapsing, and moving the wide one was not the
+ fix for it. */}
+
+ {/* Narrow screens keep the collapsed dropdown. */}
diff --git a/site/src/styles/starlight/30-toc-under-hero.css b/site/src/styles/starlight/30-toc-under-hero.css
index b6bfdc29..618f0bf4 100644
--- a/site/src/styles/starlight/30-toc-under-hero.css
+++ b/site/src/styles/starlight/30-toc-under-hero.css
@@ -1,85 +1,12 @@
-/* Contents list under the hero, instead of in the right-hand rail.
+/* Leftovers from the contents-list experiment that moved the ToC out of
+ * the right-hand rail and under the hero. That is reverted: Starlight puts
+ * the ToC in the rail on desktop and collapses it to a dropdown below,
+ * which is what was wanted -- the original complaint was that the MOBILE
+ * list was not collapsing, and moving the desktop one was not the fix.
*
- * The list is rendered by src/components/starlight/MarkdownContent.astro; see
- * that file for why it keeps the `.right-sidebar-panel` class. These rules
- * undo the parts of that class that only made sense in a fixed 208px rail.
+ * Only this one unrelated rule is still needed here.
*/
-/* Which of the two contents lists shows, and where the switch happens.
- *
- * 50rem, the width at which the left sidebar stops being a drawer and becomes
- * a permanent column. Starlight's own switch for the ToC is 72rem, so the
- * 50-72rem band showed the collapsed dropdown beside a fully expanded
- * sidebar, with a strip of empty hero above it -- one of the two navigation
- * panes collapsed and the other not, at a width with room for both. */
-@media (max-width: 49.99rem) {
- .docs-toc-under-hero {
- display: none !important;
- }
-}
-
-@media (min-width: 50rem) {
- .docs-toc-under-hero {
- display: block !important;
- }
-
- mobile-starlight-toc {
- display: none !important;
- }
-}
-
-/* The column Starlight reserves for the rail has to collapse, or the article
- keeps a dead gutter roughly a sidebar wide. TwoColumnContent.astro sizes
- .main-pane by subtracting that width, so both change together. */
-@media (min-width: 72rem) {
- .right-sidebar-container {
- display: none !important;
- }
-
- [data-has-sidebar][data-has-toc] .main-pane,
- .main-pane {
- width: 100% !important;
- }
-}
-
-/* Full content width, not the rail's 208px -- otherwise the multi-column
- rule below can only ever produce one column. */
-.docs-toc-under-hero.right-sidebar-panel {
- width: 100% !important;
- max-width: none !important;
- padding: 0 !important;
-}
-
-.docs-toc-under-hero .sl-container {
- width: auto !important;
- max-width: none !important;
-}
-
-/* The rail moves from the list onto each link. In the rail layout a single
- border on the was the rail; laid out in columns it would mark only the
- first column and leave the other two floating. A border per link gives each
- column its own continuous rail, and the active link still lights it green. */
-/* These compete with `.right-sidebar-panel ...` rules in
- 20-docs-shell-closeout.css. An earlier version of this comment claimed the
- doubled class was needed because the bundler does not emit these files in
- filename order; that was measured and is wrong for this pair -- 30 is
- emitted after 20, and the rule it competes with carries no !important, so
- these win on either count. (Emission order is not always filename order --
- 24 is emitted before 23 -- but that is not what was happening here; the
- symptom that prompted it was a stale deployed stylesheet.) */
-.docs-toc-under-hero :where(nav ul) {
- border-inline-start: 0 !important;
-}
-
-.docs-toc-under-hero :where(a) {
- margin-inline: 0 !important;
- border-inline-start: 2px solid var(--sl-color-hairline) !important;
-}
-
-.docs-toc-under-hero :where(a[aria-current='true']) {
- border-inline-start-color: var(--netscli-accent) !important;
-}
-
/* The search dialog's own failure message, injected by
scripts/docs-search-keys.ts when Pagefind neither answers nor errors.
Sized and coloured like the message it sits under, so it reads as part of
diff --git a/ux-walkthrough.md b/ux-walkthrough.md
index 303d288d..fabd4344 100644
--- a/ux-walkthrough.md
+++ b/ux-walkthrough.md
@@ -1,12 +1,21 @@
-# UX walkthrough — NetsCLI desktop app
+# UX walkthrough
-The flow an acceptance pass should walk, step by step, against a running
-build. Written from the shipped app; each step below was observed rather
+Two surfaces, each with its own journeys: the **desktop app** and the
+**website** (`site/`, netscli.com — landing page, docs and changelog).
+
+The flows an acceptance pass should walk, step by step, against a running
+build and a served site. Written from the shipped app; each step below was observed rather
than intended. Where a step depends on privileges or the local network, the
condition is stated so a failure can be attributed correctly.
-Scope: `apps/netscli-gui`. The CLI, TUI and MCP surfaces have their own
-documentation under `docs/`.
+The CLI, TUI and MCP surfaces have no walkthrough: they are exercised by
+`docs/` and by their own tests, and neither has states a person navigates.
+
+---
+
+# Desktop app
+
+Scope: `apps/netscli-gui`.
## Primary job
@@ -75,3 +84,137 @@ Recorded so an acceptance pass does not report them as new:
verify interaction findings both ways before believing either.
- The MSI has never been tested on a clean machine without WebView2
preinstalled.
+
+---
+
+# Website
+
+Scope: `site/` — the landing page, the docs, and the changelog, served as a
+static build. Written by walking a served production build at 1440px and
+375px; the structure below is what is there, not what was intended.
+
+This section did not exist while the site shipped, which is why nothing had
+ever walked it. Every journey here is one a person actually performs; a step
+that cannot be completed is a finding, not a curiosity.
+
+## Primary job
+
+**Decide whether NetsCLI is worth installing, then install it.**
+
+Everything else on the site serves that or follows from it: the docs explain
+what you just installed, and the changelog says whether to update.
+
+Search engines and language models are a second audience for the same
+content, which is why the copy has to answer a question rather than describe
+a feature. They are not a separate journey — a page that reads well for a
+person and states plainly what the tool does serves both.
+
+## Steps
+
+### A. Evaluate — "what is this, and is it for me?"
+
+1. **Land.** The hero states what NetsCLI is in one line, with the current
+ release, star count and download total beside it. Each of those three is
+ hidden until GitHub confirms it, so an unauthenticated rate limit shows
+ nothing rather than something wrong.
+2. **Scan the surfaces section** (`#surfaces`) — desktop app, terminal UI,
+ CLI, MCP server — and recognise which one is yours.
+3. **Reach the FAQ** (`#faq`), 13 questions in 4 groups: what it is, install
+ and updates, interfaces and integrations, network workflows, limits and
+ dependencies. These carry the comparison questions people actually search
+ for ("alternative to Angry IP Scanner", "replace nmap").
+4. **Leave for the source** if that is the decision — the GitHub link is in
+ the top nav and in the hero.
+
+### B. Install — "give me the command"
+
+1. **Reach `#install`**, from the nav or the hero CTA.
+2. **Pick a platform** — Windows, macOS, Linux tabs. The tab set is the
+ first choice because a command for the wrong OS is worse than none.
+3. **Pick a form** — desktop app or CLI + terminal UI. These are separate
+ products with separate package names, and the page must not blur them.
+4. **Copy a command**, or download an installer directly. Every command has
+ a copy button; the download links resolve through
+ `/releases/latest/download/…` so they never name a version that has moved
+ on.
+5. **Verify, if you care** — checksums and signing are stated with a link to
+ the verification steps.
+
+### C. Learn — "how do I do the thing"
+
+1. **Enter the docs**, from the nav or a search engine landing on a deep
+ page. Either is a first page, so every page has to stand alone.
+2. **Orient**: left sidebar for the section list, right rail for the
+ contents of this page, breadcrumb for where you are.
+3. **Search** (`Ctrl K`, or the button on narrow screens) when the nav does
+ not have the word you are thinking of.
+4. **Follow an anchor** to a heading, and share that link.
+5. **Move between pages** with the sidebar, keeping your place.
+
+### D. Check what changed — "should I update?"
+
+1. **Open the changelog**, from the nav or from a version number.
+2. **Read the newest release first**; each entry says what changed and why
+ it matters, with install, compatibility or security notes called out.
+3. **Tell released from unreleased.** An entry with no published release
+ behind it is labelled and is not linked, because a link would 404.
+
+### E. Recover — "that link was wrong"
+
+1. **Hit a 404** and get a page that says so and offers a way back, rather
+ than a dead end.
+
+### Narrow screens
+
+Each journey above has a narrow variant, and they are where this site has
+historically broken:
+
+- The section list collapses behind a menu control; the page contents
+ collapse into a dropdown under the hero.
+- Search becomes a full-screen dialog rather than an inline field.
+- The install tabs and command blocks must not overflow horizontally; a
+ command you cannot read is a command you cannot copy.
+
+## States
+
+| State | What should happen |
+| --- | --- |
+| **First paint, no JavaScript** | Every page's content is in the HTML, including the changelog entries. Nothing says "Loading". |
+| **GitHub unreachable or rate-limited** | Hero metrics stay hidden; the changelog still lists every release from the repo's own file, unlinked and marked not-yet-released. Nothing shows a stale or invented number. |
+| **A version in the changelog with no release** | Labelled "Not yet released", and deliberately not linked. |
+| **Search index unavailable** | The dialog says so in its own voice, rather than appearing empty or hanging. |
+| **Theme: dark, light, or following the system** | All three are selectable and all three are legible; the control shows which is active. |
+| **Deep-linked to an anchor** | The target heading is visible and not hidden behind sticky chrome. |
+| **Narrow viewport** | No horizontal overflow on any page. Every command block scrolls within itself rather than widening the page. |
+| **Print** | Navigation chrome is dropped; the article survives. |
+
+## Known gaps
+
+Recorded so an acceptance pass reports what is new rather than what is
+already known. All were measured on a served production build.
+
+- **Page padding is asymmetric.** At 1440px a docs page leaves 29px to the
+ left of the sidebar and 92px to the right of the contents rail.
+- **The two narrow-screen header controls do not match.** Search is
+ `rgba(17,22,29,0.9)` with a `rgba(140,149,166,0.24)` border; the menu
+ toggle is `rgba(255,255,255,0.035)` with a `rgba(255,255,255,0.1)` border.
+ Same size, same radius, different surface.
+- **The install section is dense** — 45 copy controls on one page — and the
+ relative prominence of package manager, script and direct download has not
+ been decided deliberately.
+- **Typography scale is unreviewed** and reads large at desktop widths.
+- **The coverage matrix states capabilities as Yes/No** where several rows
+ are not applicable rather than absent, and it treats the CLI, TUI and MCP
+ as fully separate when they share one binary and one core.
+- **Mobile search layout**: the clear control and the cancel affordance
+ compete, and the results panel does not extend to the bottom of the
+ viewport.
+- **Anchor links jump the page** rather than moving to the heading quietly.
+- **The sidebar's active marker shifts** when a different item is hovered.
+- **Breadcrumb separators sit low** relative to their text.
+- **The theme control's focus ring is drawn incorrectly**, and its dropdown
+ is unstyled.
+
+No `design-direction.md` covers this surface: that document scopes itself to
+the desktop app. Until one exists, "does this look right" has no written
+answer here, and that is the root of most of the list above.