diff --git a/.cache/pages.json b/.cache/pages.json index 7539e6a7a..83133af42 100644 --- a/.cache/pages.json +++ b/.cache/pages.json @@ -2,7 +2,71 @@ "about", { "articles": [ - "kubernetes-pod-disruption-budget-autoscaler-node-rotation" + "alert-fatigue-reduction-triage-actionable-alerts", + "api-deprecation-sunset-headers-consumer-migration", + "api-gateway-metrics-traces-logs-debugging", + "api-usage-metering-quotas-cost-attribution", + "argocd-sync-failures-gitops-debugging-troubleshooting", + "availability-targets-five-nines-cost-benefit-analysis", + "backpressure-load-shedding-admission-control-overload", + "blameless-postmortem-incident-analysis-systemic-causes", + "blue-green-canary-deployment-strategy-comparison", + "cdn-edge-caching-cache-keys-vary-headers", + "chaos-engineering-failure-injection-low-cost-experiments", + "ci-pipeline-caching-docker-layers-dependency-cache", + "circuit-breaker-retry-budget-cascade-failure-prevention", + "consumer-driven-contract-testing-pact-internal-apis", + "container-vulnerability-scanning-ci-shift-left-security", + "database-schema-migrations-continuous-deployment-zero-downtime", + "dead-letter-queue-design-replay-debugging", + "distributed-tracing-sampling-strategies-head-tail", + "eol-runtime-upgrade-dependency-hell-migration", + "ephemeral-preview-environments-cost-control-cleanup", + "flaky-test-diagnosis-race-conditions-e2e-stabilization", + "golden-paths-developer-experience-standardization-autonomy", + "grafana-dashboard-hygiene-pruning-actionable-metrics", + "helm-release-management-drift-detection-debugging", + "idempotent-message-handlers-deduplication-retries", + "internal-cli-kubectl-terraform-wrapper-abstraction", + "internal-developer-portal-platform-self-service-actions", + "internal-platform-api-versioning-deprecation-breaking-changes", + "kubernetes-cluster-upgrade-playbook-risk-reduction", + "kubernetes-cost-optimization-resource-sizing-spot-instances", + "kubernetes-decision-framework-when-not-to-use", + "kubernetes-dns-debugging-ndots-coredns-troubleshooting", + "kubernetes-hpa-autoscaling-metrics-tuning-latency", + "kubernetes-ingress-gateway-api-comparison-migration", + "kubernetes-multi-cluster-fleet-management-configuration", + "kubernetes-pod-resource-requests-limits-qos-classes", + "kubernetes-secrets-external-secrets-operator-csi-vault", + "legacy-code-testing-characterization-tests-seams", + "monorepo-affected-builds-remote-caching-ci-optimization", + "mtls-certificate-rotation-service-mesh-authentication", + "nginx-haproxy-reverse-proxy-production-tuning", + "on-call-rotation-small-teams-sustainable-coverage", + "opa-conftest-policy-as-code-infrastructure-guardrails", + "openapi-spec-documentation-sdk-generation-validation", + "opentelemetry-span-design-granularity-overhead", + "performance-testing-load-models-benchmark-accuracy", + "platform-architecture-control-plane-data-plane-separation", + "platform-engineering-metrics-lead-time-developer-friction", + "postgresql-connection-pooling-saturation-sizing", + "private-networking-dns-routing-tls-debugging", + "prometheus-high-cardinality-metrics-label-design", + "rate-limiting-token-bucket-leaky-bucket-implementation", + "release-quality-gates-automated-deployment-validation", + "reverse-engineering-documentation-legacy-systems", + "service-catalog-metadata-schema-ownership-tracking", + "service-decommissioning-scream-test-shutdown", + "slo-error-budget-practical-guide", + "slsa-build-provenance-artifact-signing-supply-chain", + "strangler-fig-migration-complete-guide", + "structured-logging-correlation-ids-log-schema-design", + "symptom-based-alerting-runbooks-alert-design", + "synthetic-test-data-pii-anonymization-fixtures", + "terraform-module-design-defaults-versioning-interfaces", + "terraform-state-locking-corruption-recovery-backend", + "workload-identity-federation-keyless-cloud-authentication" ] }, { diff --git a/.clinerules b/.clinerules index 2bf437998..c781c2fc3 100644 --- a/.clinerules +++ b/.clinerules @@ -27,6 +27,16 @@ There is no stop button to break out of a hung loop. possible, pass `--yes` / `-y` (e.g. `npx --yes `). - **`gh`**: use `--no-pager` where supported and avoid interactive subcommands. +### Git is read-only for the agent + +NEVER run state-changing git commands: `git commit`, `git add`, `git push`, +`git stash`, `git checkout`, `git switch`, `git restore`, `git reset`, +`git rebase`, `git merge`, `git cherry-pick`, `git tag`, etc. The user handles +all git mutations themselves — do not commit work, even when a task mirrors a +previously committed step. Read-only inspection is fine, always with +`--no-pager` per the rules above (`git status`, `git --no-pager log`, +`git --no-pager diff`, `git --no-pager show`, `git --no-pager blame`). + ### General rules for `execute_command` - Every command must be **non-interactive** and **self-terminating**. diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml index 37076c69e..ba57b98cb 100644 --- a/.github/workflows/search.yml +++ b/.github/workflows/search.yml @@ -66,7 +66,7 @@ jobs: run: | COLLECTIONS="" if [ "${{ steps.scope.outputs.crawl_articles }}" = "true" ]; then - COLLECTIONS="$COLLECTIONS --collection articles --collection deep-dive" + COLLECTIONS="$COLLECTIONS --collection articles" fi if [ "${{ steps.scope.outputs.crawl_services }}" = "true" ]; then COLLECTIONS="$COLLECTIONS --collection services" diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e9cfbe88..65c8ac095 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -301,6 +301,7 @@ "pypiserver", "pypoetry", "qdisc", + "Qdrant", "qpart", "qparts", "Qualtrics", @@ -359,6 +360,7 @@ "Swatinem", "Syft", "tanabata", + "Taskiq", "tcpdump", "tegna", "Tetris", diff --git a/SESSION.md b/SESSION.md new file mode 100644 index 000000000..139a19f5e --- /dev/null +++ b/SESSION.md @@ -0,0 +1,238 @@ +# Remove current articles, rename deep dives to articles, and remove all deep dive functionality + +## Background + +Each `src/content/articles//` directory used to contain two content items: + +- `index.mdx` — a short-form "overview" article served at `/articles/` +- `pdf.mdx` — a long-form "deep dive" served at `/deep-dive/` (loaded into the + separate `deepDives` content collection with a custom `generateId` that stripped + the `/pdf` suffix) + +The short-form pages were marked `noindex, follow` with a canonical link pointing at +their `/deep-dive/` counterpart, and `/articles/` URLs were excluded from +the sitemap via `deepDiveAliases`. This dual-URL setup caused Google indexing +problems, so we are consolidating to a single long-form article per topic at +`/articles/`. + +## Already completed (content migration) + +Committed in `5a968d90` and `ef2e347d`: + +- Deleted the original short-form `index.mdx` files. +- Renamed every `pdf.mdx` to `index.mdx`. + +Current state of each article directory: `index.mdx` (long-form), `download.mdx`, +`cover.jpg`, `download.jpg`, `diagrams/`. Consequences already in effect: + +- The `articles` collection (glob `**/index.mdx`) now loads the long-form content. No + glob change needed for `articles`. +- The `deepDives` collection (glob `**/pdf.mdx`) now matches **zero** files — all + `getCollection('deepDives')` call sites currently return empty arrays. Every such + call site must be migrated or removed (see below). + +## Implementation plan + +### 1. Content collections — `src/content.config.ts` + +- Remove the `deepDiveArticlesCollection` definition (the `defineCollection` block + with `pattern: '**/pdf.mdx'`). +- Remove the `generateDeepDiveId` helper (only used by that loader). +- Remove `deepDives: deepDiveArticlesCollection` from `export const collections`. +- Keep `articles` as-is; it now globs the former deep-dive content. +- Keep `downloads` (`**/download.mdx`) untouched — `download.mdx` files still exist. + +### 2. Routing + +- **Delete** `src/pages/deep-dive/[...slug].astro` (whole `src/pages/deep-dive/` dir). +- **301 redirects (SEO-critical).** The `/deep-dive/` URLs are the ones Google + indexed (the `/articles/` versions were `noindex`). They must permanently + redirect to `/articles/`: + - Add to `vercel.json` a `redirects` entry: + `{ "source": "/deep-dive/:path*", "destination": "/articles/:path*", "permanent": true }` + - Also replace `src/pages/deep-dive/[...slug].astro` with a redirect stub so + dev/preview behave the same as production: `getStaticPaths()` over the `articles` + collection, then `return Astro.redirect(`/articles/${Astro.params.slug}`, 301)`. + (If preferred, this stub can be deleted once Vercel redirects are verified live.) +- **Simplify** `src/pages/articles/[...slug].astro`: + - Remove the `getCollection('deepDives', ...)` call, `deepDiveIds`, and the + `canonicalPath` prop logic (`/deep-dive/` canonical). + - Remove `robotsContent` (`noindex, follow`) — articles become indexable canonical + pages. + - Remove the now-unused `canonicalPath` from `Props`. + +### 3. Switcher component (delete entirely) + +Delete `src/components/Content/Switcher/`: + +- `index.astro`, `index.css` +- `client/index.ts`, `client/selectors.ts`, `client/__tests__/` +- `server/index.ts`, `server/__tests__/` + +Call sites and plumbing: + +- `src/components/Content/Layout/index.astro` — remove the + `import ContentSwitcher from '@components/Content/Switcher/index.astro'` and the + `
` + block. +- `src/layouts/MarkdownLayout.astro` — remove the `breadcrumb-row` slot forwarding + (``). +- `src/layouts/BaseLayout.astro` — remove `hasBreadcrumbRowSlot` and its use in + `shouldRenderBreadcrumbRow` (which becomes simply `path !== '/' && path !== ''`), + the `` render block, and the ContentSwitcher comment. +- Nanostores: **no nanostore code exists for the Switcher** — it is a LitElement with + idle-prefetch logic only; nothing in `src/components/scripts/store/` references it. + (The `toggle theme switcher` locator in e2e `BasePage.ts` is the ThemePicker and is + unrelated.) No store work needed. + +### 4. Content layout — `src/components/Content/Layout/index.astro` + +- Remove `CollectionEntry<'deepDives'>` from the `article` prop union. +- Replace the `relatedContentType` conditional + (`path.startsWith('/deep-dive/') || canonicalPath ? 'deep-dive' : 'articles'`) + with a constant `'articles'` for the Carousel `type`. +- Remove the `canonicalPath` and `robotsContent` props from `Props` and the + MarkdownLayout pass-throughs if no other caller uses them after step 2 (verify with + a grep for `canonicalPath` across pages; `MarkdownLayout`/`Head` keep their own + generic support — unit tests there just need fixture updates, see step 10). + +### 5. Collection consumers — switch `deepDives` to `articles` + +- `src/pages/articles/index.astro` — `getCollection('deepDives')` → + `getCollection('articles')`; rename `allDeepDives`/`getDeepDivesForTag` locals; + Carousel `type="deep-dive"` → `type="articles"`; update copy: page description + "Browse technical deep dives…" and the "N deep dives" badge. +- `src/pages/index.astro` (homepage "Latest Insights") — Carousel + `type="deep-dive"` → `type="articles"`. +- `src/pages/rss.xml.ts` — `getCollection('deepDives')` → `'articles'`; item link + `/deep-dive/${article.id}` → `/articles/${article.id}`. +- `src/pages/print/[...slug].astro` — `Props.article` type and + `getCollection('deepDives')` → `'articles'`. The QR/copyright link already points + at `/articles/`; keep it. +- `src/components/Layout/Print/Cover/index.astro` — prop type + `CollectionEntry<'deepDives'>` → `CollectionEntry<'articles'>`. +- `src/layouts/MarkdownLayout.astro` — remove `CollectionEntry<'deepDives'>` from + the `collectionItem` union. +- `src/components/Pages/TagPage/index.astro` — card href + `` `/deep-dive/${item.id}` `` → `` `/articles/${item.id}` `` (the tag pagination + helper `src/lib/tags/pagination.ts` already queries the `articles` collection). +- `src/components/Carousel/@types/index.ts` — remove `'deep-dive': 'deepDives'` + from `collectionMap`. Item hrefs are built as `/${type}/${item.id}`, so no other + Carousel change is needed; check `server/__fixtures__/collection.fixture.ts` and + server tests for `deepDives` references. +- `src/components/Breadcrumbs/index.astro` — remove `rewriteBreadcrumbHref` + (`/deep-dive` → `/articles`) and its two call sites (JSON-LD schema item URL and + the link `href`). +- `src/components/Search/SearchResults/client/index.ts` — remove + `'deep-dive': 'Deep Dive'` from `resultTypeLabels`. + +### 6. Sitemap — `src/integrations/sitemapSerialize/` + `astro.config.ts` + +- Delete `src/integrations/sitemapSerialize/deepDiveAliases.ts` and + `src/integrations/sitemapSerialize/__tests__/deepDiveAliases.spec.ts`. (This module + generated `/articles/` paths to *exclude* from the sitemap; after + consolidation those URLs are canonical and must be *included*.) +- `astro.config.ts` — remove the `getDeepDiveArticleAliasPaths` import, the + `deepDiveArticleAliasPaths` const, and `...deepDiveArticleAliasPaths` from the + sitemap `exclude` array. `/deep-dive/*` URLs disappear from the sitemap + automatically when the route is removed. + +### 7. PDF generation — `scripts/generate-pdfs/index.mjs` + +- `collectAllSlugs()`: existence check `join(ARTICLES_DIR, e.name, 'pdf.mdx')` → + `'index.mdx'`. +- Single-slug validation: `pdf.mdx` → `index.mdx`, plus error message text + ("No pdf.mdx found", "No deep dive articles with pdf.mdx found", header usage + comment). +- No change to the render URL (`/print/`), output dir (`public/downloads/`), + or the `pdf:generate` npm script name. + +### 8. Search indexing — `scripts/search-index.py` + `.github/workflows/search.yml` + +- `scripts/search-index.py`: remove the + `CollectionConfig(name="deep-dive", url_prefix="/deep-dive", source_dir="articles", glob_pattern="**/pdf.mdx")` + entry from `COLLECTIONS`. The `articles` entry (`**/index.mdx`) now indexes the + long-form content at the correct `/articles/` URLs. +- `.github/workflows/search.yml`: in "Index changed collections", drop + `--collection deep-dive` from the `crawl_articles` branch. +- The `determine-search-index-scope` and `prune-upstash-search` composite actions are + collection-agnostic (parameterized) — no changes needed. +- **One-time operational step after deploy:** the live Upstash index contains stale + `/deep-dive/*` documents that the incremental `--no-drop` CI run will not remove + (the prune step only covers the `articles` collection). Run a full reindex once + manually — `npm run search:reindex` (drops and rebuilds the index by default) — + with production Upstash credentials. + +### 9. GitHub workflows — other + +- Grepped all of `.github/workflows/`: only `search.yml` references deep-dive/pdf. + `cron.yml`, `deployment-*.yml`, `playwright.yml`, etc. need no changes. PDFs are + generated manually (`npm run pdf:generate`) and committed under + `public/downloads/`; no CI job generates them. + +### 10. Unit tests + +- Delete: `src/components/Content/Switcher/**/__tests__/` (covered by step 3), + `src/integrations/sitemapSerialize/__tests__/deepDiveAliases.spec.ts`. +- Update fixtures that use `/deep-dive/...` example URLs: + - `src/components/Head/__tests__/Meta.spec.ts` + (`canonicalPath: '/deep-dive/example-article'` and expected URL) + - `src/components/Head/server/__tests__/structuredData.spec.ts` (same fixture; + expects `https://www.webstackbuilders.com/deep-dive/example-article`) + - `src/components/Search/SearchBar/client/__tests__/results.spec.ts` and + `index.spec.ts` (deep-dive URLs — functionally generic, update to `/articles/...` + for consistency) + +### 11. E2E tests — `test/e2e/` + +- `specs/01-smoke/dynamic-pages.spec.ts` — `articleLinkSelector = + 'a[href*="/deep-dive/"]'` → `/articles/`; URL assertion `/\/deep-dive\/.+/` → + `/\/articles\/.+/`. +- `specs/02-pages/articles.spec.ts` — expects navigation to `/deep-dive/`; + change to `/articles/`. +- `specs/02-pages/tags.spec.ts` — `a[href^="/deep-dive/"]` → `a[href^="/articles/"]`. +- `specs/07-metadata/seo-tags.spec.ts`, `open-graph.spec.ts`, + `structured-data.spec.ts` — drop the `a[href^="/deep-dive/"]` alternative from + `articleDetailLinkSelector`. +- `helpers/pageObjectModels/BreadCrumbPage.ts` — simplify `linkSelector` and update + `notFoundMessage` ("Could not find deep-dive or article detail link…"). + +### 12. Support files / housekeeping + +- `.gitignore` — no pdf/deep-dive entries exist; `public/downloads/` PDFs are + committed intentionally. Under the existing "Planning docs" section + (`CONTENT*.md`), consider adding `SESSION.md` if this plan should stay uncommitted. +- `_TODO.md` — remove the stale line about the Switcher component bug (component is + being deleted). +- `package.json` — no script renames needed (`pdf:generate`, `search:reindex` + unchanged). +- Content files: verified no MDX links to `/deep-dive/...` exist (only image + filenames like `*_deep-dive.jpg` and prose, which are fine). + +## Validation checklist + +1. `npx astro sync` — schema/types regenerate without `deepDives`. +2. `npx tsc --noEmit -p tsconfig.json --pretty false` — no dangling + `CollectionEntry<'deepDives'>` types. +3. `npm run lint:code` and `npm run check`. +4. `npm run test:unit` — Switcher/alias specs gone; updated fixtures pass. +5. `npm run build` — confirm: no `/deep-dive/` pages emitted; `/articles/` + pages present; sitemap includes `/articles/*` and excludes nothing article-related; + `rss.xml` links to `/articles/*`. +6. `npm run test:e2e` (at least `@smoke` + the updated specs). +7. Manual spot-check: `npm run dev` → article page renders long-form content at + `/articles/`, no Switcher in breadcrumb row, related-content carousel links + to `/articles/*`, `/deep-dive/` 301s to `/articles/`. +8. Optionally regenerate one PDF: start preview server, then + `node scripts/generate-pdfs/index.mjs `. + +## Post-deploy SEO / ops + +1. Verify the Vercel 301 redirect live: + `curl -I https://www.webstackbuilders.com/deep-dive/` → `301` to + `/articles/...`. +2. Run the one-time full Upstash reindex (see step 8) to purge `/deep-dive/*` search + documents. +3. In Google Search Console: submit the updated sitemap, use URL Inspection on a few + `/deep-dive/*` URLs to confirm the redirect is picked up, and monitor coverage for + the consolidated `/articles/*` URLs. diff --git a/_TODO.md b/_TODO.md index d90646fd5..6c223a791 100644 --- a/_TODO.md +++ b/_TODO.md @@ -24,7 +24,6 @@ https://aws.plainenglish.io/how-to-build-a-chatbot-using-aws-lex-and-lambda-in-2 ## Header - "Squish" Effect - Need to improve the "squish" animation where the header reduces in size on scroll down, and returns to full size on scroll up. Maybe reduce and expand the text and search / themepicker / hamburger menu sizes in place, and then slide them horizontally. -- Moving the scroll bar up quickly with the mouse seems to make the header logic break - the Switcher component and Breadcrumbs are hidden under the header - Themepicker and search icon are too big in non-squished header. Logo too - the initial presentation should be smaller. ## Resume diff --git a/astro.config.ts b/astro.config.ts index 753c12c11..383c5dd1d 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -35,7 +35,6 @@ import { testimonialsLengthWarning } from './src/integrations/TestimonialsLength import { fixContentAssetPropagation } from './src/lib/plugins/fixContentAssetPropagation' import { pwaDevAssetServer } from './src/lib/plugins/pwaDevAssetServer' import { createSerializeFunction, pagesJsonWriter } from './src/integrations/sitemapSerialize' -import { getDeepDiveArticleAliasPaths } from './src/integrations/sitemapSerialize/deepDiveAliases' // Ensure Vite's HMR websocket connects through the same exposed dev server port used by Astro. const devServerPort = Number(process.env['DEV_SERVER_PORT'] ?? 4321) @@ -45,7 +44,6 @@ const shouldEnableSentryIntegration = Boolean(sentryAuthToken) const sentryReleaseName = getPackageRelease() const astroEnvConfig = environmentalVariablesConfig as NonNullable const astroMarkdownConfig = markdownConfig as unknown as NonNullable -const deepDiveArticleAliasPaths = getDeepDiveArticleAliasPaths() const shouldSuppressViteWarning = (message: string): boolean => { return ( @@ -116,7 +114,6 @@ const standardIntegrations = [ 'testing', 'hero', 'links', - ...deepDiveArticleAliasPaths, ], }), }), diff --git a/package.json b/package.json index dd377aed5..4af4793c6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dev": "npm run sync && cross-env NODE_ENV=development FORCE_COLOR=1 npx astro dev", "dev:local": "FORCE_COLOR=1 dotenv -e .env.development -- npm run dev", "dev:local:verbose": "FORCE_COLOR=1 dotenv -e .env.development -- npm run dev -- --verbose", + "dev:stop": "astro dev stop", "format": "npm run format:code && npm run format:style", "format:code": "FORCE_COLOR=1 npx prettier --write \"@types/**/*.{js,ts}\" \"src/**/*.{js,ts,tsx,astro}\" --plugin=prettier-plugin-astro", "format:markdown": "FORCE_COLOR=1 npx markdownlint-cli2 --fix \"**/*.{md,mdx}\"", diff --git a/public/downloads/alert-fatigue-reduction-triage-actionable-alerts.pdf b/public/downloads/alert-fatigue-reduction-triage-actionable-alerts.pdf index 663d0f5e8..b779e455e 100644 Binary files a/public/downloads/alert-fatigue-reduction-triage-actionable-alerts.pdf and b/public/downloads/alert-fatigue-reduction-triage-actionable-alerts.pdf differ diff --git a/public/downloads/api-deprecation-sunset-headers-consumer-migration.pdf b/public/downloads/api-deprecation-sunset-headers-consumer-migration.pdf index 3dd112b10..edabda8a4 100644 Binary files a/public/downloads/api-deprecation-sunset-headers-consumer-migration.pdf and b/public/downloads/api-deprecation-sunset-headers-consumer-migration.pdf differ diff --git a/public/downloads/api-gateway-metrics-traces-logs-debugging.pdf b/public/downloads/api-gateway-metrics-traces-logs-debugging.pdf index bd18032f7..8818d7ae7 100644 Binary files a/public/downloads/api-gateway-metrics-traces-logs-debugging.pdf and b/public/downloads/api-gateway-metrics-traces-logs-debugging.pdf differ diff --git a/public/downloads/api-usage-metering-quotas-cost-attribution.pdf b/public/downloads/api-usage-metering-quotas-cost-attribution.pdf index fd35999b4..5c2bb2ffb 100644 Binary files a/public/downloads/api-usage-metering-quotas-cost-attribution.pdf and b/public/downloads/api-usage-metering-quotas-cost-attribution.pdf differ diff --git a/public/downloads/argocd-sync-failures-gitops-debugging-troubleshooting.pdf b/public/downloads/argocd-sync-failures-gitops-debugging-troubleshooting.pdf index cdfd0c44c..092962b81 100644 Binary files a/public/downloads/argocd-sync-failures-gitops-debugging-troubleshooting.pdf and b/public/downloads/argocd-sync-failures-gitops-debugging-troubleshooting.pdf differ diff --git a/public/downloads/availability-targets-five-nines-cost-benefit-analysis.pdf b/public/downloads/availability-targets-five-nines-cost-benefit-analysis.pdf index ba47ee0f8..ed2fbc9d5 100644 Binary files a/public/downloads/availability-targets-five-nines-cost-benefit-analysis.pdf and b/public/downloads/availability-targets-five-nines-cost-benefit-analysis.pdf differ diff --git a/public/downloads/backpressure-load-shedding-admission-control-overload.pdf b/public/downloads/backpressure-load-shedding-admission-control-overload.pdf index 8e00c9801..16bbdeca1 100644 Binary files a/public/downloads/backpressure-load-shedding-admission-control-overload.pdf and b/public/downloads/backpressure-load-shedding-admission-control-overload.pdf differ diff --git a/public/downloads/blameless-postmortem-incident-analysis-systemic-causes.pdf b/public/downloads/blameless-postmortem-incident-analysis-systemic-causes.pdf index 210d49ab8..3f1e90de9 100644 Binary files a/public/downloads/blameless-postmortem-incident-analysis-systemic-causes.pdf and b/public/downloads/blameless-postmortem-incident-analysis-systemic-causes.pdf differ diff --git a/public/downloads/blue-green-canary-deployment-strategy-comparison.pdf b/public/downloads/blue-green-canary-deployment-strategy-comparison.pdf index 951478841..1614bcf23 100644 Binary files a/public/downloads/blue-green-canary-deployment-strategy-comparison.pdf and b/public/downloads/blue-green-canary-deployment-strategy-comparison.pdf differ diff --git a/public/downloads/cdn-edge-caching-cache-keys-vary-headers.pdf b/public/downloads/cdn-edge-caching-cache-keys-vary-headers.pdf index 002c135ff..e77974669 100644 Binary files a/public/downloads/cdn-edge-caching-cache-keys-vary-headers.pdf and b/public/downloads/cdn-edge-caching-cache-keys-vary-headers.pdf differ diff --git a/public/downloads/chaos-engineering-failure-injection-low-cost-experiments.pdf b/public/downloads/chaos-engineering-failure-injection-low-cost-experiments.pdf index d2e830527..27f766355 100644 Binary files a/public/downloads/chaos-engineering-failure-injection-low-cost-experiments.pdf and b/public/downloads/chaos-engineering-failure-injection-low-cost-experiments.pdf differ diff --git a/public/downloads/ci-pipeline-caching-docker-layers-dependency-cache.pdf b/public/downloads/ci-pipeline-caching-docker-layers-dependency-cache.pdf index c09cc493b..3ca9844d0 100644 Binary files a/public/downloads/ci-pipeline-caching-docker-layers-dependency-cache.pdf and b/public/downloads/ci-pipeline-caching-docker-layers-dependency-cache.pdf differ diff --git a/public/downloads/circuit-breaker-retry-budget-cascade-failure-prevention.pdf b/public/downloads/circuit-breaker-retry-budget-cascade-failure-prevention.pdf index 98c85ac4e..ca7b793e8 100644 Binary files a/public/downloads/circuit-breaker-retry-budget-cascade-failure-prevention.pdf and b/public/downloads/circuit-breaker-retry-budget-cascade-failure-prevention.pdf differ diff --git a/public/downloads/consumer-driven-contract-testing-pact-internal-apis.pdf b/public/downloads/consumer-driven-contract-testing-pact-internal-apis.pdf index d2530f594..b0070a715 100644 Binary files a/public/downloads/consumer-driven-contract-testing-pact-internal-apis.pdf and b/public/downloads/consumer-driven-contract-testing-pact-internal-apis.pdf differ diff --git a/public/downloads/container-vulnerability-scanning-ci-shift-left-security.pdf b/public/downloads/container-vulnerability-scanning-ci-shift-left-security.pdf index 57f6e7db5..5ed8c6fb0 100644 Binary files a/public/downloads/container-vulnerability-scanning-ci-shift-left-security.pdf and b/public/downloads/container-vulnerability-scanning-ci-shift-left-security.pdf differ diff --git a/public/downloads/database-schema-migrations-continuous-deployment-zero-downtime.pdf b/public/downloads/database-schema-migrations-continuous-deployment-zero-downtime.pdf index ccf4dc7ba..2f0c39784 100644 Binary files a/public/downloads/database-schema-migrations-continuous-deployment-zero-downtime.pdf and b/public/downloads/database-schema-migrations-continuous-deployment-zero-downtime.pdf differ diff --git a/public/downloads/dead-letter-queue-design-replay-debugging.pdf b/public/downloads/dead-letter-queue-design-replay-debugging.pdf index dba78a7d0..ce193c29c 100644 Binary files a/public/downloads/dead-letter-queue-design-replay-debugging.pdf and b/public/downloads/dead-letter-queue-design-replay-debugging.pdf differ diff --git a/public/downloads/distributed-tracing-sampling-strategies-head-tail.pdf b/public/downloads/distributed-tracing-sampling-strategies-head-tail.pdf index 6fd0c4bb6..068c4b71e 100644 Binary files a/public/downloads/distributed-tracing-sampling-strategies-head-tail.pdf and b/public/downloads/distributed-tracing-sampling-strategies-head-tail.pdf differ diff --git a/public/downloads/eol-runtime-upgrade-dependency-hell-migration.pdf b/public/downloads/eol-runtime-upgrade-dependency-hell-migration.pdf index 4bfaa3730..db8aaa1cf 100644 Binary files a/public/downloads/eol-runtime-upgrade-dependency-hell-migration.pdf and b/public/downloads/eol-runtime-upgrade-dependency-hell-migration.pdf differ diff --git a/public/downloads/ephemeral-preview-environments-cost-control-cleanup.pdf b/public/downloads/ephemeral-preview-environments-cost-control-cleanup.pdf index 9ac04473b..dceb0feb8 100644 Binary files a/public/downloads/ephemeral-preview-environments-cost-control-cleanup.pdf and b/public/downloads/ephemeral-preview-environments-cost-control-cleanup.pdf differ diff --git a/public/downloads/flaky-test-diagnosis-race-conditions-e2e-stabilization.pdf b/public/downloads/flaky-test-diagnosis-race-conditions-e2e-stabilization.pdf index 3e6c94c19..d7e801b57 100644 Binary files a/public/downloads/flaky-test-diagnosis-race-conditions-e2e-stabilization.pdf and b/public/downloads/flaky-test-diagnosis-race-conditions-e2e-stabilization.pdf differ diff --git a/public/downloads/golden-paths-developer-experience-standardization-autonomy.pdf b/public/downloads/golden-paths-developer-experience-standardization-autonomy.pdf index 20d44aaa4..ac2fc2138 100644 Binary files a/public/downloads/golden-paths-developer-experience-standardization-autonomy.pdf and b/public/downloads/golden-paths-developer-experience-standardization-autonomy.pdf differ diff --git a/public/downloads/grafana-dashboard-hygiene-pruning-actionable-metrics.pdf b/public/downloads/grafana-dashboard-hygiene-pruning-actionable-metrics.pdf index 73989c956..f2ee81874 100644 Binary files a/public/downloads/grafana-dashboard-hygiene-pruning-actionable-metrics.pdf and b/public/downloads/grafana-dashboard-hygiene-pruning-actionable-metrics.pdf differ diff --git a/public/downloads/helm-release-management-drift-detection-debugging.pdf b/public/downloads/helm-release-management-drift-detection-debugging.pdf index 4a232d686..e196cf017 100644 Binary files a/public/downloads/helm-release-management-drift-detection-debugging.pdf and b/public/downloads/helm-release-management-drift-detection-debugging.pdf differ diff --git a/public/downloads/idempotent-message-handlers-deduplication-retries.pdf b/public/downloads/idempotent-message-handlers-deduplication-retries.pdf index 140b3a114..461f245e1 100644 Binary files a/public/downloads/idempotent-message-handlers-deduplication-retries.pdf and b/public/downloads/idempotent-message-handlers-deduplication-retries.pdf differ diff --git a/public/downloads/internal-cli-kubectl-terraform-wrapper-abstraction.pdf b/public/downloads/internal-cli-kubectl-terraform-wrapper-abstraction.pdf index fbb38b543..0feb852d7 100644 Binary files a/public/downloads/internal-cli-kubectl-terraform-wrapper-abstraction.pdf and b/public/downloads/internal-cli-kubectl-terraform-wrapper-abstraction.pdf differ diff --git a/public/downloads/internal-developer-portal-platform-self-service-actions.pdf b/public/downloads/internal-developer-portal-platform-self-service-actions.pdf index 7a07891d9..7aaec997a 100644 Binary files a/public/downloads/internal-developer-portal-platform-self-service-actions.pdf and b/public/downloads/internal-developer-portal-platform-self-service-actions.pdf differ diff --git a/public/downloads/internal-platform-api-versioning-deprecation-breaking-changes.pdf b/public/downloads/internal-platform-api-versioning-deprecation-breaking-changes.pdf index 2620a371d..569a98751 100644 Binary files a/public/downloads/internal-platform-api-versioning-deprecation-breaking-changes.pdf and b/public/downloads/internal-platform-api-versioning-deprecation-breaking-changes.pdf differ diff --git a/public/downloads/kubernetes-cluster-upgrade-playbook-risk-reduction.pdf b/public/downloads/kubernetes-cluster-upgrade-playbook-risk-reduction.pdf index c289a16d1..2d24aa785 100644 Binary files a/public/downloads/kubernetes-cluster-upgrade-playbook-risk-reduction.pdf and b/public/downloads/kubernetes-cluster-upgrade-playbook-risk-reduction.pdf differ diff --git a/public/downloads/kubernetes-cost-optimization-resource-sizing-spot-instances.pdf b/public/downloads/kubernetes-cost-optimization-resource-sizing-spot-instances.pdf index 8e9f8aac2..c877ec367 100644 Binary files a/public/downloads/kubernetes-cost-optimization-resource-sizing-spot-instances.pdf and b/public/downloads/kubernetes-cost-optimization-resource-sizing-spot-instances.pdf differ diff --git a/public/downloads/kubernetes-decision-framework-when-not-to-use.pdf b/public/downloads/kubernetes-decision-framework-when-not-to-use.pdf index e306b1227..ab0e25d45 100644 Binary files a/public/downloads/kubernetes-decision-framework-when-not-to-use.pdf and b/public/downloads/kubernetes-decision-framework-when-not-to-use.pdf differ diff --git a/public/downloads/kubernetes-dns-debugging-ndots-coredns-troubleshooting.pdf b/public/downloads/kubernetes-dns-debugging-ndots-coredns-troubleshooting.pdf index 07dfc3513..5f53c0ac5 100644 Binary files a/public/downloads/kubernetes-dns-debugging-ndots-coredns-troubleshooting.pdf and b/public/downloads/kubernetes-dns-debugging-ndots-coredns-troubleshooting.pdf differ diff --git a/public/downloads/kubernetes-hpa-autoscaling-metrics-tuning-latency.pdf b/public/downloads/kubernetes-hpa-autoscaling-metrics-tuning-latency.pdf index c3de6e8d3..ecb686221 100644 Binary files a/public/downloads/kubernetes-hpa-autoscaling-metrics-tuning-latency.pdf and b/public/downloads/kubernetes-hpa-autoscaling-metrics-tuning-latency.pdf differ diff --git a/public/downloads/kubernetes-ingress-gateway-api-comparison-migration.pdf b/public/downloads/kubernetes-ingress-gateway-api-comparison-migration.pdf index 0ce07bc18..855b72f59 100644 Binary files a/public/downloads/kubernetes-ingress-gateway-api-comparison-migration.pdf and b/public/downloads/kubernetes-ingress-gateway-api-comparison-migration.pdf differ diff --git a/public/downloads/kubernetes-multi-cluster-fleet-management-configuration.pdf b/public/downloads/kubernetes-multi-cluster-fleet-management-configuration.pdf index 75ab25dd5..12fec16ce 100644 Binary files a/public/downloads/kubernetes-multi-cluster-fleet-management-configuration.pdf and b/public/downloads/kubernetes-multi-cluster-fleet-management-configuration.pdf differ diff --git a/public/downloads/kubernetes-pod-resource-requests-limits-qos-classes.pdf b/public/downloads/kubernetes-pod-resource-requests-limits-qos-classes.pdf index 9aae0a8bd..b54cdef08 100644 Binary files a/public/downloads/kubernetes-pod-resource-requests-limits-qos-classes.pdf and b/public/downloads/kubernetes-pod-resource-requests-limits-qos-classes.pdf differ diff --git a/public/downloads/kubernetes-secrets-external-secrets-operator-csi-vault.pdf b/public/downloads/kubernetes-secrets-external-secrets-operator-csi-vault.pdf index fd5ac7bbf..d59929f5f 100644 Binary files a/public/downloads/kubernetes-secrets-external-secrets-operator-csi-vault.pdf and b/public/downloads/kubernetes-secrets-external-secrets-operator-csi-vault.pdf differ diff --git a/public/downloads/legacy-code-testing-characterization-tests-seams.pdf b/public/downloads/legacy-code-testing-characterization-tests-seams.pdf index 250ae4e17..ef854c53e 100644 Binary files a/public/downloads/legacy-code-testing-characterization-tests-seams.pdf and b/public/downloads/legacy-code-testing-characterization-tests-seams.pdf differ diff --git a/public/downloads/monorepo-affected-builds-remote-caching-ci-optimization.pdf b/public/downloads/monorepo-affected-builds-remote-caching-ci-optimization.pdf index ac3a8ad72..3daa4bb3b 100644 Binary files a/public/downloads/monorepo-affected-builds-remote-caching-ci-optimization.pdf and b/public/downloads/monorepo-affected-builds-remote-caching-ci-optimization.pdf differ diff --git a/public/downloads/mtls-certificate-rotation-service-mesh-authentication.pdf b/public/downloads/mtls-certificate-rotation-service-mesh-authentication.pdf index a43f15fb4..92611abd8 100644 Binary files a/public/downloads/mtls-certificate-rotation-service-mesh-authentication.pdf and b/public/downloads/mtls-certificate-rotation-service-mesh-authentication.pdf differ diff --git a/public/downloads/nginx-haproxy-reverse-proxy-production-tuning.pdf b/public/downloads/nginx-haproxy-reverse-proxy-production-tuning.pdf index bfa871eb3..9e07f6404 100644 Binary files a/public/downloads/nginx-haproxy-reverse-proxy-production-tuning.pdf and b/public/downloads/nginx-haproxy-reverse-proxy-production-tuning.pdf differ diff --git a/public/downloads/on-call-rotation-small-teams-sustainable-coverage.pdf b/public/downloads/on-call-rotation-small-teams-sustainable-coverage.pdf index c47a8b0c8..c76056649 100644 Binary files a/public/downloads/on-call-rotation-small-teams-sustainable-coverage.pdf and b/public/downloads/on-call-rotation-small-teams-sustainable-coverage.pdf differ diff --git a/public/downloads/opa-conftest-policy-as-code-infrastructure-guardrails.pdf b/public/downloads/opa-conftest-policy-as-code-infrastructure-guardrails.pdf index 7514ea8c3..d861da0a6 100644 Binary files a/public/downloads/opa-conftest-policy-as-code-infrastructure-guardrails.pdf and b/public/downloads/opa-conftest-policy-as-code-infrastructure-guardrails.pdf differ diff --git a/public/downloads/openapi-spec-documentation-sdk-generation-validation.pdf b/public/downloads/openapi-spec-documentation-sdk-generation-validation.pdf index c2c87330f..a17f7441f 100644 Binary files a/public/downloads/openapi-spec-documentation-sdk-generation-validation.pdf and b/public/downloads/openapi-spec-documentation-sdk-generation-validation.pdf differ diff --git a/public/downloads/opentelemetry-span-design-granularity-overhead.pdf b/public/downloads/opentelemetry-span-design-granularity-overhead.pdf index 610954aa5..811d0169a 100644 Binary files a/public/downloads/opentelemetry-span-design-granularity-overhead.pdf and b/public/downloads/opentelemetry-span-design-granularity-overhead.pdf differ diff --git a/public/downloads/performance-testing-load-models-benchmark-accuracy.pdf b/public/downloads/performance-testing-load-models-benchmark-accuracy.pdf index 43a4b9d3c..61ecccbab 100644 Binary files a/public/downloads/performance-testing-load-models-benchmark-accuracy.pdf and b/public/downloads/performance-testing-load-models-benchmark-accuracy.pdf differ diff --git a/public/downloads/platform-architecture-control-plane-data-plane-separation.pdf b/public/downloads/platform-architecture-control-plane-data-plane-separation.pdf index d2b3a1bd1..c8395149d 100644 Binary files a/public/downloads/platform-architecture-control-plane-data-plane-separation.pdf and b/public/downloads/platform-architecture-control-plane-data-plane-separation.pdf differ diff --git a/public/downloads/platform-engineering-metrics-lead-time-developer-friction.pdf b/public/downloads/platform-engineering-metrics-lead-time-developer-friction.pdf index 8e23a5f8f..9e27aef2c 100644 Binary files a/public/downloads/platform-engineering-metrics-lead-time-developer-friction.pdf and b/public/downloads/platform-engineering-metrics-lead-time-developer-friction.pdf differ diff --git a/public/downloads/postgresql-connection-pooling-saturation-sizing.pdf b/public/downloads/postgresql-connection-pooling-saturation-sizing.pdf index 67695c71a..568dfe840 100644 Binary files a/public/downloads/postgresql-connection-pooling-saturation-sizing.pdf and b/public/downloads/postgresql-connection-pooling-saturation-sizing.pdf differ diff --git a/public/downloads/private-networking-dns-routing-tls-debugging.pdf b/public/downloads/private-networking-dns-routing-tls-debugging.pdf index 05c36966c..7a3c266e0 100644 Binary files a/public/downloads/private-networking-dns-routing-tls-debugging.pdf and b/public/downloads/private-networking-dns-routing-tls-debugging.pdf differ diff --git a/public/downloads/prometheus-high-cardinality-metrics-label-design.pdf b/public/downloads/prometheus-high-cardinality-metrics-label-design.pdf index 811e0323e..446af0c1b 100644 Binary files a/public/downloads/prometheus-high-cardinality-metrics-label-design.pdf and b/public/downloads/prometheus-high-cardinality-metrics-label-design.pdf differ diff --git a/public/downloads/rate-limiting-token-bucket-leaky-bucket-implementation.pdf b/public/downloads/rate-limiting-token-bucket-leaky-bucket-implementation.pdf index 4826fecdf..8621b0b72 100644 Binary files a/public/downloads/rate-limiting-token-bucket-leaky-bucket-implementation.pdf and b/public/downloads/rate-limiting-token-bucket-leaky-bucket-implementation.pdf differ diff --git a/public/downloads/release-quality-gates-automated-deployment-validation.pdf b/public/downloads/release-quality-gates-automated-deployment-validation.pdf index 365327351..1765c2aba 100644 Binary files a/public/downloads/release-quality-gates-automated-deployment-validation.pdf and b/public/downloads/release-quality-gates-automated-deployment-validation.pdf differ diff --git a/public/downloads/reverse-engineering-documentation-legacy-systems.pdf b/public/downloads/reverse-engineering-documentation-legacy-systems.pdf index 9a24cdd0a..6c2d46c05 100644 Binary files a/public/downloads/reverse-engineering-documentation-legacy-systems.pdf and b/public/downloads/reverse-engineering-documentation-legacy-systems.pdf differ diff --git a/public/downloads/service-catalog-metadata-schema-ownership-tracking.pdf b/public/downloads/service-catalog-metadata-schema-ownership-tracking.pdf index 9fc1684c9..9b72ccc32 100644 Binary files a/public/downloads/service-catalog-metadata-schema-ownership-tracking.pdf and b/public/downloads/service-catalog-metadata-schema-ownership-tracking.pdf differ diff --git a/public/downloads/service-decommissioning-scream-test-shutdown.pdf b/public/downloads/service-decommissioning-scream-test-shutdown.pdf index 436155424..2fa9e3666 100644 Binary files a/public/downloads/service-decommissioning-scream-test-shutdown.pdf and b/public/downloads/service-decommissioning-scream-test-shutdown.pdf differ diff --git a/public/downloads/slo-error-budget-practical-guide.pdf b/public/downloads/slo-error-budget-practical-guide.pdf index ea2091540..f9eedfbb5 100644 Binary files a/public/downloads/slo-error-budget-practical-guide.pdf and b/public/downloads/slo-error-budget-practical-guide.pdf differ diff --git a/public/downloads/slsa-build-provenance-artifact-signing-supply-chain.pdf b/public/downloads/slsa-build-provenance-artifact-signing-supply-chain.pdf index 3260619a3..eebbf6e43 100644 Binary files a/public/downloads/slsa-build-provenance-artifact-signing-supply-chain.pdf and b/public/downloads/slsa-build-provenance-artifact-signing-supply-chain.pdf differ diff --git a/public/downloads/strangler-fig-migration-complete-guide.pdf b/public/downloads/strangler-fig-migration-complete-guide.pdf index 556a7a759..9c63a606f 100644 Binary files a/public/downloads/strangler-fig-migration-complete-guide.pdf and b/public/downloads/strangler-fig-migration-complete-guide.pdf differ diff --git a/public/downloads/structured-logging-correlation-ids-log-schema-design.pdf b/public/downloads/structured-logging-correlation-ids-log-schema-design.pdf index b2ee5804c..f818e9363 100644 Binary files a/public/downloads/structured-logging-correlation-ids-log-schema-design.pdf and b/public/downloads/structured-logging-correlation-ids-log-schema-design.pdf differ diff --git a/public/downloads/symptom-based-alerting-runbooks-alert-design.pdf b/public/downloads/symptom-based-alerting-runbooks-alert-design.pdf index 9b290d83c..907fd7225 100644 Binary files a/public/downloads/symptom-based-alerting-runbooks-alert-design.pdf and b/public/downloads/symptom-based-alerting-runbooks-alert-design.pdf differ diff --git a/public/downloads/synthetic-test-data-pii-anonymization-fixtures.pdf b/public/downloads/synthetic-test-data-pii-anonymization-fixtures.pdf index 506441e1e..68de52790 100644 Binary files a/public/downloads/synthetic-test-data-pii-anonymization-fixtures.pdf and b/public/downloads/synthetic-test-data-pii-anonymization-fixtures.pdf differ diff --git a/public/downloads/terraform-module-design-defaults-versioning-interfaces.pdf b/public/downloads/terraform-module-design-defaults-versioning-interfaces.pdf index 817542be2..4f5955986 100644 Binary files a/public/downloads/terraform-module-design-defaults-versioning-interfaces.pdf and b/public/downloads/terraform-module-design-defaults-versioning-interfaces.pdf differ diff --git a/public/downloads/terraform-state-locking-corruption-recovery-backend.pdf b/public/downloads/terraform-state-locking-corruption-recovery-backend.pdf index 2fd84fe0d..8f3dfdc28 100644 Binary files a/public/downloads/terraform-state-locking-corruption-recovery-backend.pdf and b/public/downloads/terraform-state-locking-corruption-recovery-backend.pdf differ diff --git a/public/downloads/workload-identity-federation-keyless-cloud-authentication.pdf b/public/downloads/workload-identity-federation-keyless-cloud-authentication.pdf index 5441478d4..b68477d2c 100644 Binary files a/public/downloads/workload-identity-federation-keyless-cloud-authentication.pdf and b/public/downloads/workload-identity-federation-keyless-cloud-authentication.pdf differ diff --git a/scripts/generate-pdfs/index.mjs b/scripts/generate-pdfs/index.mjs index f3647e4f4..2a3aa22e8 100644 --- a/scripts/generate-pdfs/index.mjs +++ b/scripts/generate-pdfs/index.mjs @@ -4,8 +4,8 @@ * Generate PDFs from /print/ pages using Puppeteer. * * Usage: - * - node scripts/generate-pdfs.mjs # single article - * - node scripts/generate-pdfs.mjs # all deep dive articles + * - node scripts/generate-pdfs/index.mjs # single article + * - node scripts/generate-pdfs/index.mjs # all articles * - OR npm run pdf:generate * * Requires a running server (dev or preview) at localhost:4321. @@ -60,7 +60,7 @@ const LAYOUT_SETTLE_DELAY_MS = 500 const collectAllSlugs = () => { if (!existsSync(ARTICLES_DIR)) return [] return readdirSync(ARTICLES_DIR, { withFileTypes: true }) - .filter(e => e.isDirectory() && existsSync(join(ARTICLES_DIR, e.name, 'pdf.mdx'))) + .filter(e => e.isDirectory() && existsSync(join(ARTICLES_DIR, e.name, 'index.mdx'))) .map(e => e.name) .sort() } @@ -279,10 +279,10 @@ if (!(await checkServer())) { } if (slugFilter) { - const pdfMdx = join(ARTICLES_DIR, slugFilter, 'pdf.mdx') - if (!existsSync(pdfMdx)) { - console.error(`ERROR: No pdf.mdx found for "${slugFilter}"`) - console.error(`Expected: ${pdfMdx}`) + const indexMdx = join(ARTICLES_DIR, slugFilter, 'index.mdx') + if (!existsSync(indexMdx)) { + console.error(`ERROR: No index.mdx found for "${slugFilter}"`) + console.error(`Expected: ${indexMdx}`) process.exit(1) } } @@ -290,7 +290,7 @@ if (slugFilter) { const slugs = slugFilter ? [slugFilter] : collectAllSlugs() if (slugs.length === 0) { - console.error('ERROR: No deep dive articles with pdf.mdx found.') + console.error('ERROR: No articles with index.mdx found.') process.exit(1) } diff --git a/scripts/search-index.py b/scripts/search-index.py index c073fc8e5..3593f33e5 100644 --- a/scripts/search-index.py +++ b/scripts/search-index.py @@ -45,7 +45,6 @@ class CollectionConfig: # Collections to index COLLECTIONS: Final[list[CollectionConfig]] = [ CollectionConfig(name="articles", url_prefix="/articles", source_dir="articles", glob_pattern="**/index.mdx"), - CollectionConfig(name="deep-dive", url_prefix="/deep-dive", source_dir="articles", glob_pattern="**/pdf.mdx"), CollectionConfig(name="case-studies", url_prefix="/case-studies", source_dir="case-studies", glob_pattern="**/index.mdx"), CollectionConfig(name="services", url_prefix="/services", source_dir="services", glob_pattern="**/index.md"), ] diff --git a/src/components/Breadcrumbs/index.astro b/src/components/Breadcrumbs/index.astro index c6714ac50..7eaed1a20 100644 --- a/src/components/Breadcrumbs/index.astro +++ b/src/components/Breadcrumbs/index.astro @@ -40,10 +40,6 @@ applyRenderSentryContext({ }, }) -function rewriteBreadcrumbHref(href: string): string { - return href === '/deep-dive' ? '/articles' : href -} - const breadcrumbSchema = site && breadcrumbs.length > 1 ? JSON.stringify({ @@ -53,7 +49,7 @@ const breadcrumbSchema = '@type': 'ListItem', position: index + 1, name: item.label, - item: new URL(rewriteBreadcrumbHref(item.href), site).href, + item: new URL(item.href, site).href, })), }) : null @@ -76,7 +72,7 @@ const breadcrumbSchema = ) : ( {item.label} diff --git a/src/components/Carousel/@types/index.ts b/src/components/Carousel/@types/index.ts index fa6f76a08..e8e2cc31b 100644 --- a/src/components/Carousel/@types/index.ts +++ b/src/components/Carousel/@types/index.ts @@ -8,7 +8,6 @@ export type CarouselNavigationMode = 'wrap' | 'bounded' export const collectionMap = { 'case-studies': 'caseStudies', articles: 'articles', - 'deep-dive': 'deepDives', } as const export type CollectionMap = typeof collectionMap diff --git a/src/components/Content/Layout/index.astro b/src/components/Content/Layout/index.astro index bb5470939..873e92c6a 100644 --- a/src/components/Content/Layout/index.astro +++ b/src/components/Content/Layout/index.astro @@ -1,6 +1,6 @@ --- /** - * Shared layout for article and deep-dive item views. + * Shared layout for article item views. * * Wraps MarkdownLayout and fills its named slots (hero-image, * after-content, related-content) so page files only need to @@ -10,7 +10,6 @@ import type { CollectionEntry } from 'astro:content' import { Picture } from 'astro:assets' import MarkdownLayout from '@layouts/MarkdownLayout.astro' import Carousel from '@components/Carousel/index.astro' -import ContentSwitcher from '@components/Content/Switcher/index.astro' import Shares from '@components/Social/Shares/index.astro' import WebMentions from '@components/WebMentions/index.astro' @@ -18,11 +17,7 @@ export type Props = { /** Resolved author display name */ author: string /** Content collection entry */ - article: CollectionEntry<'articles'> | CollectionEntry<'deepDives'> - /** Canonical URL path when it differs from the rendered page path */ - canonicalPath?: string - /** Explicit robots meta content when the page needs non-default indexing behavior */ - robotsContent?: string + article: CollectionEntry<'articles'> /** Canonical URL path, e.g. '/articles/my-slug' */ path: string /** Estimated reading time label */ @@ -31,20 +26,17 @@ export type Props = { section: string } -const { author, article, canonicalPath, path, readingTime, robotsContent, section } = Astro.props -const relatedContentType = path.startsWith('/deep-dive/') || canonicalPath ? 'deep-dive' : 'articles' +const { author, article, path, readingTime, section } = Astro.props --- - {/** SLOT: breadcrumb-row */} -
- -
- {/** SLOT: hero-image */} { article.data.cover && ( @@ -113,7 +100,7 @@ const relatedContentType = path.startsWith('/deep-dive/') || canonicalPath ? 'de variant="suggested" currentSlug={article.id} limit={5} - type={relatedContentType} + type="articles" showReadMore={false} /> diff --git a/src/components/Content/Switcher/client/__tests__/index.spec.ts b/src/components/Content/Switcher/client/__tests__/index.spec.ts deleted file mode 100644 index 3a2af4c03..000000000 --- a/src/components/Content/Switcher/client/__tests__/index.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -// @vitest-environment node - -import { beforeEach, describe, expect, test } from 'vitest' -import { experimental_AstroContainer as AstroContainer } from 'astro/container' -import SwitcherAstro from '@components/Content/Switcher/index.astro' -import type { ContentSwitcherElement } from '@components/Content/Switcher/client' -import type { WebComponentModule } from '@components/scripts/@types/webComponentModule' -import { executeRender } from '@test/unit/helpers/litRuntime' - -type SwitcherModule = WebComponentModule - -describe('ContentSwitcherElement', () => { - let container: AstroContainer - - beforeEach(async () => { - container = await AstroContainer.create() - }) - - const runComponentRender = async ( - args: Parameters[1], - assertion: (_context: { element: ContentSwitcherElement }) => Promise | void - ): Promise => { - await executeRender({ - container, - component: SwitcherAstro, - moduleSpecifier: '@components/Content/Switcher/client/index', - args, - waitForReady: async (element: ContentSwitcherElement) => { - await element.updateComplete - }, - assert: async ({ element, module, renderResult }) => { - expect(renderResult).toContain(`<${module.registeredName}`) - await assertion({ element }) - }, - }) - } - - test('renders overview state links with deep-dive toggle target', async () => { - await runComponentRender( - { - props: { - currentVariant: 'overview', - slug: 'my-article', - }, - }, - async ({ element }) => { - const overviewLink = element.querySelector('#overview-label') as HTMLAnchorElement | null - const deepDiveLink = element.querySelector('#deep-dive-label') as HTMLAnchorElement | null - const switchLink = element.querySelector( - '[data-switcher-toggle]' - ) as HTMLAnchorElement | null - - expect(overviewLink?.getAttribute('href')).toBe('/articles/my-article') - expect(deepDiveLink?.getAttribute('href')).toBe('/deep-dive/my-article') - expect(switchLink?.getAttribute('href')).toBe('/deep-dive/my-article') - expect(switchLink?.getAttribute('aria-checked')).toBe('false') - } - ) - }) - - test('renders deep-dive state with overview toggle target', async () => { - await runComponentRender( - { - props: { - currentVariant: 'deep-dive', - slug: 'my-article', - }, - }, - async ({ element }) => { - const overviewLink = element.querySelector('#overview-label') as HTMLAnchorElement | null - const deepDiveLink = element.querySelector('#deep-dive-label') as HTMLAnchorElement | null - const switchLink = element.querySelector( - '[data-switcher-toggle]' - ) as HTMLAnchorElement | null - - expect(overviewLink?.getAttribute('href')).toBe('/articles/my-article') - expect(deepDiveLink?.getAttribute('href')).toBe('/deep-dive/my-article') - expect(switchLink?.getAttribute('href')).toBe('/articles/my-article') - expect(switchLink?.getAttribute('aria-checked')).toBe('true') - } - ) - }) - - test('normalizes slugs with leading and trailing slashes', async () => { - await runComponentRender( - { - props: { - currentVariant: 'overview', - slug: '/nested/path/', - }, - }, - async ({ element }) => { - const overviewLink = element.querySelector('#overview-label') as HTMLAnchorElement | null - const deepDiveLink = element.querySelector('#deep-dive-label') as HTMLAnchorElement | null - - expect(overviewLink?.getAttribute('href')).toBe('/articles/nested/path') - expect(deepDiveLink?.getAttribute('href')).toBe('/deep-dive/nested/path') - } - ) - }) - - test('prefetches the alternate variant href on idle', async () => { - await runComponentRender( - { - props: { - currentVariant: 'overview', - slug: 'my-article', - }, - }, - async () => { - await new Promise(resolve => setTimeout(resolve, 200)) - - const prefetchLink = document.head.querySelector( - 'link[rel="prefetch"][href="/deep-dive/my-article"]' - ) as HTMLLinkElement | null - - expect(prefetchLink).toBeTruthy() - } - ) - }) -}) diff --git a/src/components/Content/Switcher/client/index.ts b/src/components/Content/Switcher/client/index.ts deleted file mode 100644 index 8ca1074dd..000000000 --- a/src/components/Content/Switcher/client/index.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { LitElement, html } from 'lit' -import { defineCustomElement } from '@components/scripts/utils' -import type { WebComponentModule } from '@components/scripts/@types/webComponentModule' -import { queryPrefetchLink } from './selectors' - -export type ContentVariant = 'overview' | 'deep-dive' - -const prefetchedHrefs = new Set() - -const normalizeSlug = (slug: string): string => slug.replace(/^\/+|\/+$/g, '') - -const queueIdlePrefetch = (work: () => void): void => { - if (typeof window === 'undefined') { - return - } - - let hasRun = false - const runOnce = () => { - if (hasRun) { - return - } - - hasRun = true - work() - } - - if (typeof window.requestIdleCallback === 'function') { - window.requestIdleCallback(() => runOnce()) - window.setTimeout(runOnce, 150) - return - } - - window.setTimeout(runOnce, 150) -} - -const prefetchDocument = (href: string): void => { - if (typeof document === 'undefined' || typeof window === 'undefined' || !href) { - return - } - - if (prefetchedHrefs.has(href)) { - return - } - - prefetchedHrefs.add(href) - - const existingLink = queryPrefetchLink(document.head, href) - if (!existingLink) { - const link = document.createElement('link') - link.rel = 'prefetch' - link.href = href - document.head.append(link) - } - - if (typeof window.fetch === 'function') { - void window - .fetch(href, { - credentials: 'same-origin', - }) - .catch(() => { - /* Best-effort warmup only. */ - }) - } -} - -/** - * Builds a route href for a content variant and slug. - */ -const buildVariantHref = (variant: ContentVariant, slug: string): string => { - const normalizedSlug = normalizeSlug(slug) - - if (variant === 'overview') { - return normalizedSlug ? `/articles/${normalizedSlug}` : '/articles' - } - - return normalizedSlug ? `/deep-dive/${normalizedSlug}` : '/deep-dive' -} - -export class ContentSwitcherElement extends LitElement { - static registeredName = 'content-switcher' - - static override properties = { - currentVariant: { type: String, attribute: 'current-variant' }, - slug: { type: String }, - } - - declare currentVariant: ContentVariant - declare slug: string - - constructor() { - super() - this.currentVariant = 'overview' - this.slug = '' - } - - protected override createRenderRoot() { - return this - } - - public override connectedCallback(): void { - super.connectedCallback() - this.prefetchAlternateVariant() - } - - protected override updated(changedProperties: Map): void { - if (changedProperties.has('currentVariant') || changedProperties.has('slug')) { - this.prefetchAlternateVariant() - } - } - - private prefetchAlternateVariant(): void { - const alternateVariant = this.currentVariant === 'deep-dive' ? 'overview' : 'deep-dive' - const href = buildVariantHref(alternateVariant, this.slug) - - queueIdlePrefetch(() => { - prefetchDocument(href) - }) - } - - protected override render() { - const isDeepDive = this.currentVariant === 'deep-dive' - const overviewHref = buildVariantHref('overview', this.slug) - const deepDiveHref = buildVariantHref('deep-dive', this.slug) - const switchHref = isDeepDive ? overviewHref : deepDiveHref - - const commonLabelClasses = 'text-xs no-underline hover:no-underline focus-visible:no-underline' - - const overviewLabelClass = isDeepDive - ? 'content-switcher-label content-switcher-label--overview text-content-offset hover:text-content' + - ' ' + - commonLabelClasses - : 'content-switcher-label content-switcher-label--overview text-page-inverse' + - ' ' + - commonLabelClasses - - const deepDiveLabelClass = isDeepDive - ? 'content-switcher-label content-switcher-label--deep-dive text-page-inverse' + - ' ' + - commonLabelClasses - : 'content-switcher-label content-switcher-label--deep-dive text-content-offset hover:text-content' + - ' ' + - commonLabelClasses - - return html` -
- ` - } -} - -export const registerWebComponent = async (tagName = ContentSwitcherElement.registeredName) => { - defineCustomElement(tagName, ContentSwitcherElement) -} - -export const registerContentSwitcherComponent = registerWebComponent - -export const webComponentModule: WebComponentModule = { - registeredName: ContentSwitcherElement.registeredName, - componentCtor: ContentSwitcherElement, - registerWebComponent, -} diff --git a/src/components/Content/Switcher/client/selectors.ts b/src/components/Content/Switcher/client/selectors.ts deleted file mode 100644 index aadc79693..000000000 --- a/src/components/Content/Switcher/client/selectors.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isType1Element } from '@components/scripts/assertions/elements' - -export const SELECTORS = { - prefetchLink: 'link[rel="prefetch"]', -} as const - -export const queryPrefetchLink = (root: ParentNode, href: string): HTMLLinkElement | null => { - if (!href) { - return null - } - - const candidate = root.querySelector(`${SELECTORS.prefetchLink}[href="${href}"]`) - return isType1Element(candidate) && candidate.tagName === 'LINK' - ? (candidate as HTMLLinkElement) - : null -} diff --git a/src/components/Content/Switcher/index.astro b/src/components/Content/Switcher/index.astro deleted file mode 100644 index e183d2299..000000000 --- a/src/components/Content/Switcher/index.astro +++ /dev/null @@ -1,42 +0,0 @@ ---- -import { parseContentPath } from './server' -import { BuildError } from '@lib/errors/BuildError' -import './index.css' - -export type Props = { - /** The URL path component e.g. 'articles/my-article' */ - path?: string - /** Legacy current variant for client rendering */ - currentVariant?: 'overview' | 'deep-dive' - /** Legacy slug for client rendering */ - slug?: string -} - -const { path, currentVariant: legacyCurrentVariant, slug: legacySlug = '' } = Astro.props - -let currentVariant: 'overview' | 'deep-dive' -let slug: string - -if (path) { - const parsed = parseContentPath(path) - currentVariant = parsed.currentVariant === 'articles' ? 'overview' : 'deep-dive' - slug = parsed.slug -} else if (legacyCurrentVariant) { - currentVariant = legacyCurrentVariant - slug = legacySlug -} else { - throw new BuildError('Content/Switcher: expected either `path` or `currentVariant` prop.', { - phase: 'compilation', - filePath: 'src/components/Content/Switcher/index.astro', - tool: 'content-switcher', - }) -} ---- - - - - diff --git a/src/components/Content/Switcher/index.css b/src/components/Content/Switcher/index.css deleted file mode 100644 index f30b4e54e..000000000 --- a/src/components/Content/Switcher/index.css +++ /dev/null @@ -1,7 +0,0 @@ -.content-switcher-track[aria-checked='false'] .content-switcher-thumb { - transform: translateX(0.375rem); -} - -.content-switcher-track[aria-checked='true'] .content-switcher-thumb { - transform: translateX(1.125rem); -} diff --git a/src/components/Content/Switcher/server/__tests__/index.spec.ts b/src/components/Content/Switcher/server/__tests__/index.spec.ts deleted file mode 100644 index c3f941328..000000000 --- a/src/components/Content/Switcher/server/__tests__/index.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { BuildError } from '@lib/errors/BuildError' -import { parseContentPath } from '../index' - -describe('parseContentPath', () => { - it('parses an articles path into currentVariant and slug', () => { - expect(parseContentPath('articles/my-article')).toEqual({ - currentVariant: 'articles', - slug: 'my-article', - }) - }) - - it('parses a deep-dive path into currentVariant and slug', () => { - expect(parseContentPath('deep-dive/web-vitals')).toEqual({ - currentVariant: 'deep-dive', - slug: 'web-vitals', - }) - }) - - it('normalizes leading and trailing slashes', () => { - expect(parseContentPath('/articles/my-article/')).toEqual({ - currentVariant: 'articles', - slug: 'my-article', - }) - }) - - it('normalizes extra whitespace around path', () => { - expect(parseContentPath(' /deep-dive/platform-reliability/ ')).toEqual({ - currentVariant: 'deep-dive', - slug: 'platform-reliability', - }) - }) - - it('preserves nested slug segments', () => { - expect(parseContentPath('articles/category/subcategory/my-article')).toEqual({ - currentVariant: 'articles', - slug: 'category/subcategory/my-article', - }) - }) - - it('returns an empty slug when only the variant is provided', () => { - expect(parseContentPath('articles')).toEqual({ - currentVariant: 'articles', - slug: '', - }) - }) - - it('throws BuildError for unsupported variant', () => { - expect(() => parseContentPath('services/my-service')).toThrow(BuildError) - expect(() => parseContentPath('services/my-service')).toThrow( - 'Content/Switcher: invalid path variant.' - ) - }) - - it('throws BuildError for an empty path', () => { - const parse = () => parseContentPath(' ') - - expect(parse).toThrow(BuildError) - expect(parse).toThrow('Content/Switcher: invalid path variant.') - }) - - it('includes detailed context in the thrown BuildError', () => { - try { - parseContentPath('/unknown/thing') - throw new Error('Expected parseContentPath to throw BuildError') - } catch (error) { - const typedError = error as BuildError - - expect(typedError).toBeInstanceOf(BuildError) - expect(typedError.phase).toBe('compilation') - expect(typedError.tool).toBe('content-switcher') - expect(typedError.filePath).toBe('src/components/Content/Switcher/server/index.ts') - expect(typedError.cause).toEqual({ - providedPath: '/unknown/thing', - normalizedPath: 'unknown/thing', - receivedVariant: 'unknown', - expectedVariants: ['articles', 'deep-dive'], - }) - } - }) -}) diff --git a/src/components/Content/Switcher/server/index.ts b/src/components/Content/Switcher/server/index.ts deleted file mode 100644 index 8528267e3..000000000 --- a/src/components/Content/Switcher/server/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BuildError } from '@lib/errors/BuildError' -type ContentVariant = 'articles' | 'deep-dive' - -export const parseContentPath = (path: string) => { - const normalizedPath = path.trim().replace(/^\/+|\/+$/g, '') - const [variant, ...slugParts] = normalizedPath.split('/').filter(Boolean) - const candidateVariant = variant ?? '' - - const isSupportedVariant = (value: string): value is ContentVariant => { - return value === 'articles' || value === 'deep-dive' - } - - if (!isSupportedVariant(candidateVariant)) { - const errorDetails = { - providedPath: path, - normalizedPath, - receivedVariant: variant ?? null, - expectedVariants: ['articles', 'deep-dive'], - } - - throw new BuildError( - new Error('Content/Switcher: invalid path variant.', { cause: errorDetails }), - { - phase: 'compilation', - filePath: 'src/components/Content/Switcher/server/index.ts', - tool: 'content-switcher', - } - ) - } - - return { - currentVariant: candidateVariant, - slug: slugParts.join('/'), - } -} diff --git a/src/components/Head/__tests__/Meta.spec.ts b/src/components/Head/__tests__/Meta.spec.ts index 488763eee..9d0990f92 100644 --- a/src/components/Head/__tests__/Meta.spec.ts +++ b/src/components/Head/__tests__/Meta.spec.ts @@ -17,7 +17,7 @@ describe('Meta (Astro)', () => { pageTitle: 'Example Article', pageDescription: 'Example description', path: '/articles/example-article', - canonicalPath: '/deep-dive/example-article', + canonicalPath: '/articles/example-article-canonical', }, request: new Request('https://example.com/articles/example-article'), partial: false, @@ -27,7 +27,7 @@ describe('Meta (Astro)', () => { const document = new JSDOM(renderedHtml).window.document expect(document.querySelector('link[rel="canonical"]')?.getAttribute('href')).toBe( - 'https://example.com/deep-dive/example-article' + 'https://example.com/articles/example-article-canonical' ) }) diff --git a/src/components/Head/server/__tests__/structuredData.spec.ts b/src/components/Head/server/__tests__/structuredData.spec.ts index 6da462025..ff3601c20 100644 --- a/src/components/Head/server/__tests__/structuredData.spec.ts +++ b/src/components/Head/server/__tests__/structuredData.spec.ts @@ -39,7 +39,7 @@ describe('getSchemas', () => { const schemas = getSchemas( createStructuredDataParams({ path: '/articles/example-article', - canonicalPath: '/deep-dive/example-article', + canonicalPath: '/articles/example-article-canonical', contentType: 'article', publishDate: new Date('2026-01-01T00:00:00.000Z'), }) @@ -49,7 +49,7 @@ describe('getSchemas', () => { const article = parsed.find(schema => schema['@type'] === 'Article') const breadcrumbs = parsed.find(schema => schema['@type'] === 'BreadcrumbList') - expect(article?.url).toBe('https://www.webstackbuilders.com/deep-dive/example-article') + expect(article?.url).toBe('https://www.webstackbuilders.com/articles/example-article-canonical') expect(breadcrumbs?.itemListElement?.[1]?.item).toBe( 'https://www.webstackbuilders.com/articles' ) diff --git a/src/components/Layout/Print/Cover/index.astro b/src/components/Layout/Print/Cover/index.astro index 6afa5fe25..c5a197e55 100644 --- a/src/components/Layout/Print/Cover/index.astro +++ b/src/components/Layout/Print/Cover/index.astro @@ -7,7 +7,7 @@ import Image from '@components/Image/index.astro' import coverImage from './cover.jpg' export interface Props { - article: CollectionEntry<'deepDives'> + article: CollectionEntry<'articles'> } const { article } = Astro.props diff --git a/src/components/Pages/TagPage/index.astro b/src/components/Pages/TagPage/index.astro index 0333abde4..86dce82e7 100644 --- a/src/components/Pages/TagPage/index.astro +++ b/src/components/Pages/TagPage/index.astro @@ -120,7 +120,7 @@ const path = buildTagPagePath(tag, currentPage) Tagged content {paginatedContent.map(item => { - const href = `/deep-dive/${item.id}` + const href = `/articles/${item.id}` return (
@@ -148,7 +148,7 @@ const path = buildTagPagePath(tag, currentPage)
- Deep Dive + Article