From 909344434f9e8a39fb4e84fa6c9266c217cf296b Mon Sep 17 00:00:00 2001 From: folathecoder Date: Wed, 14 Oct 2026 13:43:25 +0100 Subject: [PATCH 01/20] fix(ui): restore docs layout below lg and unclip the header menus Docs pages were unusable on phones and tablets: the sticky mobile page selector is a w-full child of the docs flex row, so it took the whole width and the article collapsed to 0px (one word per line, 11k px tall). The layout now stacks in a column below lg. - Portal the mobile docs sheet to body. Its parent bar uses backdrop-filter, which made it the containing block for the fixed sheet, so the sheet rendered thousands of pixels off screen. Lock body scroll while open, close on Escape and on route change. - Drop overflow-hidden from the 56px header; it clipped both the mobile menu and the version dropdown. The mobile menu is now an absolute panel below the bar, and the dropdown closes on outside click and Escape. - Compact theme toggle uses resolvedTheme so it works under "system". - Search trigger is icon-only below sm instead of wrapping its label. - Changelog search results and the version menu deep-link to the release anchor. - Copy-code buttons and heading anchors stay visible on touch devices. - Feedback widget no longer pretends to submit a comment: "No" links to a pre-filled GitHub issue for the page. - Remove the phantom /feed.xml autodiscovery link and blog branch. - Delete unused footer, hero, terminal demo, code tabs, reveal, share links, brand icons, and blog types; drop the motion dependency. - Align manifest and viewport theme colors with the real palette. Co-Authored-By: Claude Fable 5.1 --- package-lock.json | 69 ----------- package.json | 1 - src/app/changelog.xml/route.ts | 1 + src/app/docs/[section]/[slug]/page.tsx | 2 +- src/app/docs/layout.tsx | 6 +- src/app/globals.css | 10 ++ src/app/layout.tsx | 4 +- src/app/manifest.ts | 4 +- src/components/docs/feedback-widget.tsx | 93 +++++++------- src/components/docs/sidebar.tsx | 82 ++++++++---- src/components/home/code-tabs.tsx | 60 --------- src/components/home/hero.tsx | 125 ------------------- src/components/home/terminal-demo.tsx | 158 ------------------------ src/components/icons/brands.tsx | 43 ------- src/components/reveal.tsx | 36 ------ src/components/search.tsx | 7 +- src/components/share-links.tsx | 96 -------------- src/components/site-footer.tsx | 118 ------------------ src/components/site-header.tsx | 75 ++++++++--- src/components/theme-toggle.tsx | 6 +- src/lib/content/search.ts | 2 +- src/lib/content/types.ts | 15 --- src/lib/rss.ts | 6 +- src/lib/seo.ts | 1 - 24 files changed, 193 insertions(+), 827 deletions(-) delete mode 100644 src/components/home/code-tabs.tsx delete mode 100644 src/components/home/hero.tsx delete mode 100644 src/components/home/terminal-demo.tsx delete mode 100644 src/components/icons/brands.tsx delete mode 100644 src/components/reveal.tsx delete mode 100644 src/components/share-links.tsx delete mode 100644 src/components/site-footer.tsx diff --git a/package-lock.json b/package-lock.json index c6748e5..ca508c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "gray-matter": "^4.0.3", "lucide-react": "^1.23.0", "minisearch": "^7.2.0", - "motion": "^12.42.2", "next": "16.2.10", "next-themes": "^0.4.6", "react": "19.2.4", @@ -4129,33 +4128,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/framer-motion": { - "version": "12.42.2", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", - "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.42.2", - "motion-utils": "^12.39.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6602,47 +6574,6 @@ "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", "license": "MIT" }, - "node_modules/motion": { - "version": "12.42.2", - "resolved": "https://registry.npmjs.org/motion/-/motion-12.42.2.tgz", - "integrity": "sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==", - "license": "MIT", - "dependencies": { - "framer-motion": "^12.42.2", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/motion-dom": { - "version": "12.42.2", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz", - "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.39.0" - } - }, - "node_modules/motion-utils": { - "version": "12.39.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", - "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 5a27f90..3c74b74 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "gray-matter": "^4.0.3", "lucide-react": "^1.23.0", "minisearch": "^7.2.0", - "motion": "^12.42.2", "next": "16.2.10", "next-themes": "^0.4.6", "react": "19.2.4", diff --git a/src/app/changelog.xml/route.ts b/src/app/changelog.xml/route.ts index fbbec9b..6e4f661 100644 --- a/src/app/changelog.xml/route.ts +++ b/src/app/changelog.xml/route.ts @@ -25,6 +25,7 @@ export async function GET() { renderRss({ title: "agentling changelog", path: "/changelog", + feedPath: "/changelog.xml", description: "Every agentling release, documented.", items, }), diff --git a/src/app/docs/[section]/[slug]/page.tsx b/src/app/docs/[section]/[slug]/page.tsx index 84270c4..b4fa18d 100644 --- a/src/app/docs/[section]/[slug]/page.tsx +++ b/src/app/docs/[section]/[slug]/page.tsx @@ -141,7 +141,7 @@ export default async function DocPage({ params }: DocPageParams) { {/* Was this helpful? Widget */}
- +
{/* Prev / Next within section */} diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx index 524e32c..3fb1e3d 100644 --- a/src/app/docs/layout.tsx +++ b/src/app/docs/layout.tsx @@ -9,9 +9,11 @@ export default async function DocsLayout({ const sections = await getDocSections(); return ( -
+ // Column flow below lg so the sticky mobile page selector stacks above + // the content instead of competing with it for horizontal space. +
-
+
{children}
diff --git a/src/app/globals.css b/src/app/globals.css index 03ae851..03f8385 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -313,6 +313,16 @@ body { padding: 0.5rem 1.25rem; } +/* Hover-revealed controls have no hover on touch devices: keep them visible. */ +@media (hover: none) { + .prose :where(h2, h3, h4) .heading-anchor { + opacity: 0.6; + } + [data-markdown-root] [data-copy-button] { + opacity: 1; + } +} + @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f300893..3679c47 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -74,8 +74,8 @@ export const metadata: Metadata = { export const viewport: Viewport = { themeColor: [ - { media: "(prefers-color-scheme: dark)", color: "#000000" }, - { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#0f1012" }, + { media: "(prefers-color-scheme: light)", color: "#fafaf7" }, ], }; diff --git a/src/app/manifest.ts b/src/app/manifest.ts index a7deccb..d5e5769 100644 --- a/src/app/manifest.ts +++ b/src/app/manifest.ts @@ -9,8 +9,8 @@ export default function manifest(): MetadataRoute.Manifest { description: SITE_TAGLINE, start_url: "/", display: "standalone", - background_color: "#0b0e14", - theme_color: "#0b0e14", + background_color: "#0f1012", + theme_color: "#0f1012", icons: [ { src: "/icon.svg", diff --git a/src/components/docs/feedback-widget.tsx b/src/components/docs/feedback-widget.tsx index d455136..83b2a66 100644 --- a/src/components/docs/feedback-widget.tsx +++ b/src/components/docs/feedback-widget.tsx @@ -3,85 +3,78 @@ import { Check, ThumbsDown, ThumbsUp } from "lucide-react"; import { useState } from "react"; -export function FeedbackWidget() { - const [voted, setVoted] = useState<"yes" | "no" | null>(null); - const [comment, setComment] = useState(""); - const [submitted, setSubmitted] = useState(false); +import { GITHUB_DOCS_URL } from "@/lib/site"; - function handleVote(choice: "yes" | "no") { - setVoted(choice); - } +/** + * Lightweight "was this helpful?" prompt. There is no feedback backend, so + * a "no" hands the reader straight to a pre-filled GitHub issue instead of + * pretending to collect a comment. + */ +export function FeedbackWidget({ + pageTitle, + pagePath, +}: { + pageTitle: string; + pagePath: string; +}) { + const [voted, setVoted] = useState<"yes" | "no" | null>(null); - function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - setSubmitted(true); - } + const issueUrl = `${GITHUB_DOCS_URL}/issues/new?${new URLSearchParams({ + title: `Docs feedback: ${pageTitle}`, + body: `Page: ${pagePath}\n\nWhat was missing or unclear?\n\n`, + }).toString()}`; - if (submitted) { + if (voted === "yes") { return (
- - Thank you for your feedback. + + Glad it helped.
); } return (
-
+
Was this page helpful?
- {voted && ( -
- -
- setComment(e.target.value)} - placeholder="Your thoughts..." - className="flex-1 rounded border border-border bg-background px-2.5 py-1 text-xs text-foreground placeholder:text-faint focus:border-accent focus:outline-none" - /> - -
-
+ {voted === "no" && ( +

+ Sorry about that. Tell us what was missing in a{" "} + + GitHub issue + + , pre-filled with this page. +

)}
); diff --git a/src/components/docs/sidebar.tsx b/src/components/docs/sidebar.tsx index e19c3c8..1be49b7 100644 --- a/src/components/docs/sidebar.tsx +++ b/src/components/docs/sidebar.tsx @@ -3,7 +3,8 @@ import { ChevronDown, FileText, X } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useState, useSyncExternalStore } from "react"; +import { createPortal } from "react-dom"; import type { DocSection } from "@/lib/content/types"; import { GITHUB_URL } from "@/lib/site"; @@ -17,6 +18,8 @@ const DIATAXIS_HINTS: Record = { reference: "Reference", }; +const emptySubscribe = () => () => {}; + function SectionNav({ sections, onNavigate }: { sections: DocSection[]; onNavigate?: () => void }) { const pathname = usePathname(); @@ -117,6 +120,29 @@ function SectionNav({ sections, onNavigate }: { sections: DocSection[]; onNaviga export function DocsSidebar({ sections }: { sections: DocSection[] }) { const [mobileOpen, setMobileOpen] = useState(false); const pathname = usePathname(); + const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false); + + // Close the sheet whenever the route changes. + const [lastPathname, setLastPathname] = useState(pathname); + if (lastPathname !== pathname) { + setLastPathname(pathname); + setMobileOpen(false); + } + + // Lock page scroll and close on Escape while the sheet is open. + useEffect(() => { + if (!mobileOpen) return; + const previous = document.body.style.overflow; + document.body.style.overflow = "hidden"; + function onKeyDown(event: KeyboardEvent) { + if (event.key === "Escape") setMobileOpen(false); + } + window.addEventListener("keydown", onKeyDown); + return () => { + document.body.style.overflow = previous; + window.removeEventListener("keydown", onKeyDown); + }; + }, [mobileOpen]); const current = sections .flatMap((section) => @@ -127,13 +153,41 @@ export function DocsSidebar({ sections }: { sections: DocSection[] }) { ) .find((page) => page.href === pathname); + // Rendered through a portal: the sticky bar below uses backdrop-filter, + // which would otherwise become the containing block for this fixed sheet. + const sheet = ( +
+
+ + Documentation Index + + +
+
+ setMobileOpen(false)} /> +
+
+ ); + return ( <> {/* Mobile: sticky page selector bar */} -
+
- - {mobileOpen && ( -
- {/* Top close header bar */} -
- - Documentation Index - - -
- {/* Nav content */} -
- setMobileOpen(false)} /> -
-
- )}
+ {mobileOpen && mounted && createPortal(sheet, document.body)} + {/* Desktop: 260px Left Map Rail */}