Skip to content

Latest commit

 

History

History
351 lines (324 loc) · 21.7 KB

File metadata and controls

351 lines (324 loc) · 21.7 KB

Christitus.com Astro Specification

Product goal

Build https://christitus.com/ as a fast, durable Astro publication and creator hub. The redesign should make articles, downloads, live streams, newsletter signup, topics, and YouTube immediately discoverable while preserving the archive, integrations, and public URLs accumulated by the Hugo site.

Users and primary workflows

  • Readers find current and historical technology guides through the homepage, categories, tags, archive, search, feeds, and existing inbound links.
  • Viewers browse livestream recordings and play a selected YouTube video with synchronized Twitch chat when replay data exists.
  • Customers reach digital downloads, troubleshooting, and recommendations.
  • Subscribers use the newsletter form and its existing reCAPTCHA-backed service.
  • Maintainers publish Markdown and refresh livestream/chat data through the existing Python and GitHub Actions automation.

Required experience

Visual system

  • Use a modern tech-editorial design that retains the existing Chris Titus Tech logo and cyan #47c4f1 accent.
  • Provide polished dark and light themes using custom CSS, strong typography, restrained effects, responsive cards, and consistent spacing tokens.
  • Use Astro components and vanilla JavaScript. No Bootstrap, Tailwind, React, Vue, or other client UI runtime is required.

Navigation and homepage

  • Header destinations are Articles, Downloads, Live, Newsletter, and Topics, with search, theme toggle, and a YouTube action. The logo links home.
  • Forums, Recommendations, Archive, RSS, legal pages, and social destinations remain available through secondary/footer navigation.
  • /videos/ redirects to the public YouTube channel because the source page is empty.
  • The homepage contains one primary and two secondary featured articles, creator action cards, topic discovery, recent livestreams, latest articles, and newsletter/YouTube promotion.
  • featuredOrder accepts only 1, 2, or 3 and is globally unique; each value selects its matching homepage slot only when that post is production-eligible. A draft or future curated post does not render and leaves the slot for fallback. Remaining slots use production-eligible unselected posts sorted by publication key descending, then case-sensitive canonical URL ascending. Offset timestamps normalize to UTC for that key; date-only values map to midnight in America/Chicago on their stated date.

Articles and discovery

  • Article pages include title, publish date, reading time, categories, tags, featured media, reading progress, responsive table of contents, sharing, related posts, lazy ads, and lazy Utterances comments.
  • List, category, and tag pages use ten posts per page and retain Hugo-compatible pagination routes. Page-one aliases redirect to the canonical list URL.
  • Search accepts /search/?s=<query>, fetches /index.json on demand, searches title/content/tags/categories, and exposes loading, results, empty, and error states accessibly.
  • Archive, RSS directory, downloads, newsletter, recommendations, privacy, refund, terms, and 404 pages use the shared redesigned shell.

Editorial authoring

  • npm run new:post -- "<title>" [--date YYYY-MM-DD] [--category "<name>" ...] provides the Astro replacement for Hugo archetypes. The date defaults to the current calendar day in America/Chicago; explicit dates use strict YYYY-MM-DD. The date determines both front matter and the src/content/posts/<year>/ directory.
  • Slugs are deterministic: trim the title, apply Unicode NFKD normalization, remove combining marks, lowercase it, replace each maximal run outside ASCII [a-z0-9] with one hyphen, trim leading/trailing hyphens, and reject an empty result. The scaffolder writes <slug>.md, emits /<slug>/ as the explicit URL, and sets the image to images/<year>-thumbs/<slug>.webp.
  • The template includes the title, date, URL, image, selected categories, empty tags, draft: true, and a <!--more--> summary boundary. The command refuses to overwrite an existing destination file. It serializes the title as a JSON-compatible double-quoted YAML scalar, parses the generated front matter, and requires the parsed title to equal the input exactly before writing.
  • --category is repeatable. When it is omitted, an interactive terminal prompts for one or more choices; non-interactive use fails instead of guessing. Canonical choices are Android, ChromeOS, Development, FreeBSD, Hardware, Linux, MacOS, Misc, Networking, Software Dev, Titus, Virtualization, Windows, Windows Server, and YouTube. Values outside this exact set fail validation.
  • New posts use the canonical MacOS spelling, while migrated macOS and macos values remain unchanged. The canonical-set check applies to --category input; it rejects those historical variants for new posts. The loader accepts only these exact URL/value pairs: /2020-buyers-guide/ + macOS, /change-wallpaper/ + macOS, /macos-sysadmin-tips/ + macos, /opencore-mac/ + macOS, and /zed-editor/ + macOS. Only content loading and schema validation apply this pair allowlist to manually authored content and migrated fixtures; scaffolder input always rejects both historical spellings. The opposite legacy spelling fails at each allowlisted URL. Software Dev generates the normalized taxonomy route /categories/software-dev/.
  • Generated posts use the same content schema and validation path as manually authored Markdown. Before writing, the scaffolder virtually inserts the candidate and builds the complete resulting public route inventory using the same route-contract code as validation: explicit post URLs, derived standalone page and collection/taxonomy routes, aliases, pagination, feeds and utility endpoints, redirect sources, and static public endpoints. This includes new pagination, taxonomy, and feed outputs caused by the candidate. Collision keys enforce one leading and trailing slash and collapse repeated slashes without changing character case. Thus /Post/ and /post/ remain distinct, while /post, post/, and /post// collide with /post/. A second key maps routes to emitted paths, so /foo/ collides with static /foo/index.html and / collides with /index.html. Candidate URLs also collide with a file/directory conflict: no emitted file path may be a strict ancestor of another emitted file path. Redirect patterns are separate from emitted-route entries. Every candidate-induced route, including new taxonomy, pagination, feed, alias, and collection routes, is matched against each exact, wildcard, or parameterized redirect source under the deployed grammar. Redirect patterns are never compared with themselves; existing pattern-to- pattern behavior remains governed by the ordered redirect contract and is not a scaffolder collision. Comparison never rewrites historical front matter. Invalid categories, dates, route/output collisions, and existing destination files fail with actionable errors.

Livestreams

  • .github/workflows/update-livestreams.yml is the scheduled workflow for both livestream metadata and chat replays. Its chained jobs use the same managed data branch, and a concurrency group with cancel-in-progress: false serializes every accepted run. Each run resets the managed branch to its exact master base and performs a full current-state source reconciliation. Each job checks out its predecessor's emitted SHA and verifies the branch has not moved. After both data jobs, the workflow builds the generated site and validates its routes. The final publisher proves both the captured master base and managed-branch final SHA are unchanged, proves the candidate changes only allowlisted generated data, and fast-forwards that exact SHA directly to master without a pull request. Drift fails closed and the next accepted run refetches current data. A separate scheduled/workflow-run watchdog uses actions: read and issues: write to detect queue-limit cancellation or rejection through the Actions API and open/update a durable tracking issue. The watchdog closes the alert only after every required job succeeds and the managed data branch equals master.
  • data/livestreams.json retains updated and items. Items require videoId, title, description, thumbnail, date, and publishedAt; twitchVodId and hasChatReplay remain optional. The Python automation continues to use publishedAt when matching unmatched Twitch VODs.
  • The archive features the newest valid stream and paginates remaining streams at 24 items per static page. Deleted/private entries are not rendered.
  • /live-streams/player/?v=<videoId> validates the ID, redirects invalid values to the archive, loads YouTube playback, and displays metadata for known IDs.
  • Chat JSON is requested only when present. Missing chat keeps video full width. Loaded chat supports embedded badges/emotes, seek resynchronization, play/ pause synchronization, automatic scrolling, and manual resume. Chat scrolls independently without moving the page. The player and chat remain side by side when both are usable at that width; on narrow portrait screens, the video stays directly above the viewport-bounded chat so both are visible together when the player section is brought into view.

Content and rendering model

  • Astro loads post Markdown from src/content/posts/ and standalone page Markdown from the repository's existing src/content/ tree.
  • Content with Hugo build.render: never, including the duplicate src/content/live-streams.md source, remains excluded from Astro routes. The renderable src/content/live-streams/_index.md owns /live-streams/.
  • Published posts require valid title, date, and url. New posts require at least one category, but the loader accepts the existing published posts with empty category lists only for /2022-recap/, /worst-tech-of-2022/, and /youtube-telegram-scams/, and renders those posts without category links. The allowlist is explicit in source and tests; any other uncategorized post fails validation. Draft defaults to false: an omitted field is published, and only explicit draft: true is excluded. Duplicate normalized URLs fail the build.
  • Production captures one build instant and reuses it for every content query. Front matter with an explicit offset is parsed as an instant and is eligible at or before that instant. A date-only YYYY-MM-DD value is eligible for the entire matching day: compare it to the America/Chicago calendar date that contains the build instant, inclusively. Timestamp values without an explicit offset fail schema validation. Production excludes draft: true and later content with this shared predicate.
  • npm run dev:content starts Astro in a local-only content-preview mode that includes drafts and future content. Standard npm run dev uses production filtering. npm run build ignores/rejects content-preview mode and always produces the production route contract; a Cloudflare preview is an npm run build artifact in dist/, not the local content-preview mode.
  • Preserve historical front-matter extensions. In particular, the tables object on /bad-windows-defender/ supplies both table shortcodes and must survive typed parsing; a test asserts its existing headers and values render.
  • Historical raw HTML remains renderable. <!--more--> defines the preferred summary boundary; otherwise a plain-text excerpt is generated.
  • The compatibility renderer supports the only Hugo shortcodes present in published content: youtube, x, notice, table, and shopify. Unsupported active shortcode syntax is a build error. Hugo examples inside inline or fenced code remain escaped literal content and are excluded from this gate.
  • YouTube embeds use privacy-enhanced URLs and descriptive titles. X embeds have a usable link fallback. Notice and table output is semantic and accessible.
  • Static images, fonts, downloads, chat JSON, and custom files remain under public/. Move or copy the tracked src/content/posts/2023/english.png into the Astro public asset tree so /posts/2023/english.png remains byte-identical and routable. The site may use Cloudflare image transforms with direct fallback.

Public interfaces and URL compatibility

  • Canonical published url values are immutable unless a task explicitly includes redirects and migration approval.
  • Preserve /, /page/N/, categories, tags, category/tag pagination, top-level utility pages, all post URLs, legacy source-path aliases, and curated rules in _redirects.
  • Preserve /index.json with entries shaped as title, tags, categories, contents, and absolute permalink.
  • Preserve /index.xml, category/tag RSS endpoints, /rss/, and /sitemap.xml. Drafts and sitemap-disabled pages are excluded where expected.
  • Canonical, previous/next pagination links, Open Graph, Twitter cards, WebSite/WebPage/BlogPosting/Breadcrumb JSON-LD, favicons, and feed discovery must remain valid.
  • Move supported path rules from the tracked root _redirects file into Astro's copied public tree. Pages file redirects must use relative sources. Configure HTTP/HTTPS www.christitus.com/* canonicalization as a Cloudflare Single Redirect wildcard rule matching http*://www.christitus.com/* and targeting https://christitus.com/${2} with query preservation; ${1} is the optional scheme s, while ${2} is the path capture. These are zone-rule wildcard captures, not the Pages _redirects :splat placeholder.
  • Replace the unsupported external /winget 200 proxy with a supported /winget external redirect to https://github.com/ChrisTitusTech/winutil/releases/latest/download/winutil.ps1. Clients following redirects must receive the release script successfully; no Worker/Function proxy is introduced.
  • Preserve public/_headers security and feed caching behavior, but replace the Hugo /css/* and /js/* immutable rules. Only fingerprinted /_astro/* assets receive one-year immutable caching; copied CSS and JavaScript use a revalidating or bounded non-immutable policy.

Integrations, security, and privacy

  • Retain Cloudflare Web Analytics, Google ads, Utterances, a first-party CTT Store handoff, YouTube, Twitch chat data, newsletter reCAPTCHA, and configured social links. The downloads page links to canonical CTT Store product, account, support, and contact routes rather than loading the Shopify Buy Button SDK, so product details, localization, checkout, and fulfillment stay on the store's maintained customer experience.
  • Cloudflare Web Analytics loads with defer on every page so no-interaction pageviews remain counted. Ads, comments, media APIs, and search data load lazily or after user intent where practical.
  • Never commit or log YouTube API keys, Twitch credentials, access tokens, private keys, or environment files.
  • CI runs on pull requests and pushes. Repository rules require type checks, unit tests, production build, route validation, and browser tests to pass for ordinary code changes, including for administrators. The livestream workflow does not rely on token-suppressed push or pull-request events: after both data jobs it installs the pinned Node dependencies, builds the production site, and validates generated routes on the exact candidate SHA. Publication then rechecks the captured master base, managed-branch head, and generated-data path allowlist before a non-force fast-forward push of that SHA to master. The workflow token has no broader publication path and no GitHub App or reserved tag is required.
  • Repository rules require pull-request branches to be current with master so checks cannot be reused against a newer base. Merge queue is excluded from the cutover contract.
  • CodeQL, dependency review, Dependabot, and npm audit cover the new JavaScript supply chain. High and critical findings must be resolved or explicitly waived before merge.

Accessibility, compatibility, and performance

  • Target WCAG 2.2 AA. All controls are keyboard operable, have visible focus, and expose correct labels/states. Navigation, search, dialogs, pagination, media, tables, and forms use semantic markup.
  • Support current stable Chrome, Firefox, Safari, and Edge plus current mobile Safari and Chrome. Automated browser tests run Chromium, Firefox, and WebKit projects; manual release evidence covers real Safari, Edge, mobile Safari, and mobile Chrome where Playwright engines are not identical to the shipped browser. Core content remains readable without JavaScript.
  • Respect prefers-reduced-motion and avoid layout-dependent animation.
  • Lighthouse CI runs against a local production server using its pinned mobile preset and Chromium dependency. It measures /, /my-ai-workflow/, /categories/linux/, and /live-streams/ three times and gates on the median run. Each representative route must score at least 90 for performance, accessibility, best practices, and SEO, with LCP under 3 seconds and CLS under 0.1. Tool versions and profile settings are committed with the lockfile so local and CI runs are reproducible.
  • Local fonts, responsive images, lazy third-party scripts, and minimal client JavaScript are required performance boundaries.

Non-goals

  • Server-side rendering, Cloudflare Workers application logic, accounts, a CMS, comments hosted by this repository, or a JavaScript single-page application.
  • Rewriting historical article prose or normalizing all historical taxonomy spelling during the framework migration.
  • Replacing the Python livestream/chat data automation or changing its secret contract.
  • Redesigning the logo or introducing a new brand identity.

Acceptance criteria

  • npm ci and npm run validate pass from a clean checkout using Node 24.
  • All non-draft, non-future content builds; draft and scheduled fixtures are absent from production routes, search, feeds, and sitemap; every supported shortcode has tested output; and no unresolved Hugo template syntax appears outside literal code or pre content in generated pages.
  • The generated route/redirect contract covers the Hugo baseline, including mixed-case URLs, feeds, pagination, aliases, and static files.
  • The production artifact contains _headers and supported _redirects; tests assert /_astro/* is immutable, copied CSS/JS are not immutable, and security/feed rules remain present. Preview/production request tests verify the www canonical redirect, legacy path redirect, and followed /winget response rather than relying on file presence alone.
  • Automated browser tests cover home, post, taxonomy, search, downloads, newsletter, livestream archive/player states, redirects, and 404 behavior in Chromium, Firefox, and WebKit projects.
  • Content validation fixtures prove the three legacy category exceptions pass and a newly added uncategorized post fails. A homepage-selection fixture proves that a future-dated, non-draft post cannot fill a featured slot. A published fixture without a draft field remains present in routes, search, feeds, sitemap, and homepage selection.
  • Content fixtures prove that only the five exact legacy URLs accept macOS or macos in their current URL/value pairs; the opposite spelling at each URL and manually authored content using either value elsewhere fail. Homepage fixtures reject duplicate and out-of-range featuredOrder values, exclude draft and future curated posts, and resolve equal-date fallbacks by case-sensitive canonical URL.
  • Fixed-clock tests cover offset-bearing timestamps immediately before, equal to, and after the build instant; date-only values before, equal to, and after the America/Chicago build date; and consistent filtering across homepage, routes, search, feeds, and sitemap. Local npm run dev:content includes draft and future fixtures, while standard development and production builds exclude them; Cloudflare-preview artifacts never include them. An offsetless timestamp fixture fails schema validation.
  • Scaffolder fixtures verify template output under a fixed America/Chicago clock, explicit dates, slug edge cases, every canonical category, repeatable category flags, non-interactive failures, draft defaults, rejection of macOS and macos as new category inputs, acceptance of both in migrated fixtures, case-sensitive URL preservation, trailing-slash collision variants, emitted output paths, derived standalone and collection/taxonomy routes, pagination, aliases, feeds, utility endpoints, exact and wildcard/ parameterized redirects, static endpoints, file/directory ancestor conflicts, YAML metacharacters, colons, #, quotes, backslashes, newlines, exact parsed- title round trips, and overwrite protection. Fixtures virtually insert candidates and cover newly induced pagination, taxonomy, feed, alias, and collection outputs in exact and wildcard/parameterized redirect-overlap checks without redirect self-comparisons. The three empty-category URL exceptions remain covered separately by the content-validation fixtures.
  • The pinned Lighthouse CI profile passes all representative routes for three runs using the median thresholds defined above.
  • Manual desktop/mobile and light/dark review is recorded with screenshots; keyboard navigation and third-party fallbacks are exercised.
  • Cloudflare production uses Node 24, npm run build, and dist; preview deployments use the same build contract.
  • The retained data workflow remains enabled. Its data, chat, generated-site validation, and exact fast-forward publication jobs must all succeed, and the managed data branch must equal master after publication.
  • CI, security checks, local review, independent review, and all actionable review threads are clean before merge.