@dogsbay/docs-layout 0.2.0-beta.44 → 0.2.0-beta.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/docs-layout",
3
- "version": "0.2.0-beta.44",
3
+ "version": "0.2.0-beta.46",
4
4
  "description": "Standard documentation layout components for Dogsbay",
5
5
  "type": "module",
6
6
  "exports": {
@@ -29,8 +29,8 @@
29
29
  "./json-ld": "./src/json-ld.ts"
30
30
  },
31
31
  "dependencies": {
32
- "@dogsbay/ui": "0.2.0-beta.44",
33
- "@dogsbay/primitives": "0.2.0-beta.44"
32
+ "@dogsbay/ui": "0.2.0-beta.46",
33
+ "@dogsbay/primitives": "0.2.0-beta.46"
34
34
  },
35
35
  "devDependencies": {
36
36
  "vitest": "^3.0.0"
@@ -44,7 +44,7 @@ import VersionSwitcher from "./VersionSwitcher.astro";
44
44
  import LocaleSwitcher from "./LocaleSwitcher.astro";
45
45
  import { filterNavByAxis } from "./nav-filter.js";
46
46
  import type { LlmProviderName } from "./llm-actions.js";
47
- import { jsonLdTypeFor, normalizeCustomJsonLd } from "./json-ld.js";
47
+ import { jsonLdTypeFor, normalizeCustomJsonLd, buildArticleJsonLd } from "./json-ld.js";
48
48
  import { resolveTagKeywords } from "./tag-list-data.js";
49
49
 
50
50
  interface NavItem {
@@ -548,15 +548,16 @@ const tagKeywords = resolveTagKeywords(tags, tagLabels);
548
548
  // search engines key off for educational / tutorial / reference
549
549
  // SERP rendering. See `json-ld.ts` for the full mapping table.
550
550
  const articleJsonLd = (ogType === "article" && tagKeywords.length > 0)
551
- ? {
552
- "@context": "https://schema.org",
553
- "@type": jsonLdTypeFor(pageType),
554
- headline: title,
555
- keywords: tagKeywords.join(", "),
556
- ...(metaDescription ? { description: metaDescription } : {}),
557
- ...(metaOgImage ? { image: metaOgImage } : {}),
558
- ...(computedCanonical ? { url: computedCanonical } : {}),
559
- }
551
+ ? buildArticleJsonLd({
552
+ type: jsonLdTypeFor(pageType),
553
+ title,
554
+ siteName,
555
+ keywords: tagKeywords,
556
+ description: metaDescription,
557
+ image: metaOgImage,
558
+ url: computedCanonical,
559
+ headings,
560
+ })
560
561
  : undefined;
561
562
 
562
563
  // `customJsonLd` accepts either a single object or an array.
package/src/json-ld.ts CHANGED
@@ -53,3 +53,80 @@ export function normalizeCustomJsonLd(
53
53
  if (Array.isArray(raw)) return raw;
54
54
  return [raw];
55
55
  }
56
+
57
+ export interface BuildArticleJsonLdOptions {
58
+ /** Already-resolved Schema.org `@type` (output of jsonLdTypeFor). */
59
+ type: string;
60
+ title: string;
61
+ /** Site name — used as the `provider` Organization for Course. */
62
+ siteName: string;
63
+ keywords: string[];
64
+ description?: string;
65
+ image?: string;
66
+ url?: string;
67
+ /**
68
+ * Page headings — used to synthesize `step[]` for HowTo. Each
69
+ * H2 (or H3 if no H2s exist) becomes a HowToStep with a deep
70
+ * link to the heading id.
71
+ */
72
+ headings?: ReadonlyArray<{ depth: number; slug: string; text: string }>;
73
+ }
74
+
75
+ /**
76
+ * Build the JSON-LD payload for the page's primary structured-data
77
+ * block, shaped per `@type`. The earlier implementation emitted
78
+ * Article-shaped fields (`headline`) regardless of @type, which
79
+ * meant HowTo / Course pages failed Google's Rich Results
80
+ * requirements (HowTo needs `name` + `step`, Course needs `name` +
81
+ * `description` + `provider`). See
82
+ * `packages/cli/src/audit/rules/seo/json-ld-required-fields.ts`
83
+ * for the validator that catches this.
84
+ *
85
+ * Schema.org's `name` is a Thing-level field accepted by every
86
+ * @type, so we always emit it. `headline` is added on top for
87
+ * Article-family types (Article, TechArticle) because Google's
88
+ * Rich Results validator specifically requires it there. HowTo
89
+ * gets a synthesized `step[]` from the page's H2 headings (each
90
+ * H2 = one procedure step); Course gets a `provider` Organization
91
+ * built from `siteName`.
92
+ */
93
+ export function buildArticleJsonLd(
94
+ opts: BuildArticleJsonLdOptions,
95
+ ): Record<string, unknown> {
96
+ const { type, title, siteName, keywords, description, image, url, headings } =
97
+ opts;
98
+
99
+ const block: Record<string, unknown> = {
100
+ "@context": "https://schema.org",
101
+ "@type": type,
102
+ name: title,
103
+ keywords: keywords.join(", "),
104
+ };
105
+
106
+ if (type === "Article" || type === "TechArticle") {
107
+ block.headline = title;
108
+ }
109
+
110
+ if (description) block.description = description;
111
+ if (image) block.image = image;
112
+ if (url) block.url = url;
113
+
114
+ if (type === "HowTo") {
115
+ let stepHeadings = (headings ?? []).filter((h) => h.depth === 2);
116
+ if (stepHeadings.length === 0) {
117
+ stepHeadings = (headings ?? []).filter((h) => h.depth === 3);
118
+ }
119
+ block.step = stepHeadings.map((h) => ({
120
+ "@type": "HowToStep",
121
+ name: h.text,
122
+ url: url ? `${url}#${h.slug}` : `#${h.slug}`,
123
+ }));
124
+ }
125
+
126
+ if (type === "Course") {
127
+ block.provider = { "@type": "Organization", name: siteName };
128
+ if (!block.description) block.description = title;
129
+ }
130
+
131
+ return block;
132
+ }