@escape-game-over/atlas 0.1.115 → 0.1.116

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.
@@ -8,7 +8,7 @@ import type { ImageAsset } from "../image.ts";
8
8
  import type { IsoDate } from "../types.ts";
9
9
  import { absoluteUrl, type HttpsUrl, joinUrl, type UrlPath } from "../url.ts";
10
10
  import { organizationId } from "./ids.ts";
11
- import { alternateName, type JsonLdNode } from "./node.ts";
11
+ import { alternateName, type JsonLdNode, listed } from "./node.ts";
12
12
  import { postalAddress } from "./place.ts";
13
13
 
14
14
  export interface OrganizationInput {
@@ -172,36 +172,27 @@ export function organization(brand: OrganizationInput): JsonLdNode {
172
172
  "@id": organizationId(brand.url),
173
173
  name: brand.name,
174
174
  url: brand.url,
175
- ...alternateName(brand.name, brand.alternateName),
176
- ...(brand.description === undefined
177
- ? {}
178
- : { description: brand.description }),
179
- ...(brand.foundingDate === undefined
180
- ? {}
181
- : { foundingDate: brand.foundingDate }),
182
- ...(brand.logo === undefined
183
- ? {}
184
- : { logo: joinUrl(brand.url, brand.logo) }),
185
- ...(brand.image === undefined || brand.image.length === 0
186
- ? {}
187
- : {
188
- image: brand.image.map((photo) =>
189
- absoluteUrl(brand.url, photo.src, "organization photo")
190
- ),
191
- }),
192
- ...(brand.legalName === undefined
193
- ? {}
194
- : { legalName: brand.legalName }),
195
- ...(brand.vatID === undefined ? {} : { vatID: brand.vatID }),
175
+ alternateName: alternateName(brand.name, brand.alternateName),
176
+ description: brand.description,
177
+ foundingDate: brand.foundingDate,
178
+ logo:
179
+ brand.logo === undefined
180
+ ? undefined
181
+ : joinUrl(brand.url, brand.logo),
182
+ image: listed(brand.image)?.map((photo) =>
183
+ absoluteUrl(brand.url, photo.src, "organization photo")
184
+ ),
185
+ legalName: brand.legalName,
186
+ vatID: brand.vatID,
196
187
  // E.164, as on the business: the only form that identifies a number
197
188
  // unambiguously enough to match a company against a listing.
198
- ...(brand.telephone === undefined
199
- ? {}
200
- : { telephone: e164(brand.telephone) }),
201
- ...(brand.email === undefined ? {} : { email: brand.email }),
202
- ...(brand.sameAs === undefined ? {} : { sameAs: brand.sameAs }),
203
- ...(brand.address === undefined
204
- ? {}
205
- : { address: postalAddress(brand.address) }),
189
+ telephone:
190
+ brand.telephone === undefined ? undefined : e164(brand.telephone),
191
+ email: brand.email,
192
+ sameAs: brand.sameAs,
193
+ address:
194
+ brand.address === undefined
195
+ ? undefined
196
+ : postalAddress(brand.address),
206
197
  };
207
198
  }
@@ -37,13 +37,9 @@ export function postalAddress(address: PostalAddress): JsonLdNode {
37
37
  "@type": "PostalAddress",
38
38
  streetAddress: address.street,
39
39
  addressLocality: address.locality,
40
- ...(address.postalCode === undefined
41
- ? {}
42
- : { postalCode: address.postalCode }),
40
+ postalCode: address.postalCode,
43
41
  addressCountry: address.country,
44
- ...(address.region === undefined
45
- ? {}
46
- : { addressRegion: address.region }),
42
+ addressRegion: address.region,
47
43
  };
48
44
  }
49
45
 
@@ -11,7 +11,7 @@ import {
11
11
  import type { NonEmpty } from "../types.ts";
12
12
  import type { HttpsUrl } from "../url.ts";
13
13
  import type { BusinessId, OrganizationId } from "./ids.ts";
14
- import { type JsonLdNode, schemaType } from "./node.ts";
14
+ import { type JsonLdNode, reference, schemaType } from "./node.ts";
15
15
  import { quantitativeValue } from "./quantity.ts";
16
16
 
17
17
  export interface ProductInput {
@@ -91,7 +91,7 @@ export function product(item: ProductInput): JsonLdNode {
91
91
  name: item.name,
92
92
  description: item.description,
93
93
  url: item.url,
94
- ...(item.image === undefined ? {} : { image: item.image }),
94
+ image: item.image,
95
95
  offers: offersFor(item),
96
96
  };
97
97
  }
@@ -116,30 +116,24 @@ function offerFor(
116
116
  "@type": "Offer",
117
117
  // An `Offer` is a `Thing`, so it can carry a `name`; it is what tells
118
118
  // the adults' price from the children's.
119
- ...(name === undefined ? {} : { name }),
119
+ name,
120
120
  price: amount,
121
121
  priceCurrency: item.price.currency,
122
122
  availability: "https://schema.org/InStock",
123
123
  url: item.url,
124
- ...(band === undefined
125
- ? {}
126
- : {
127
- // What makes two prices a table rather than a contradiction:
128
- // each one says the quantity it applies at.
129
- //
130
- // The quantity is passed as written, not through `tierRange`:
131
- // the two take the same `number | { min, max }`, and widening
132
- // first would publish a band of one as `minValue: 2,
133
- // maxValue: 2` — a range across a single size, where `value:
134
- // 2` is the same claim said plainly.
135
- eligibleQuantity: {
136
- ...quantitativeValue(band.quantity),
137
- ...(unit === undefined ? {} : { unitText: unit }),
138
- },
139
- }),
140
- ...(item.seller === undefined
141
- ? {}
142
- : { seller: { "@id": item.seller } }),
124
+ // What makes two prices a table rather than a contradiction:
125
+ // each one says the quantity it applies at.
126
+ //
127
+ // The quantity is passed as written, not through `tierRange`:
128
+ // the two take the same `number | { min, max }`, and widening
129
+ // first would publish a band of one as `minValue: 2,
130
+ // maxValue: 2` — a range across a single size, where `value:
131
+ // 2` is the same claim said plainly.
132
+ eligibleQuantity:
133
+ band === undefined
134
+ ? undefined
135
+ : { ...quantitativeValue(band.quantity), unitText: unit },
136
+ seller: reference(item.seller),
143
137
  };
144
138
  }
145
139
 
@@ -1,7 +1,7 @@
1
1
  import type { HttpsUrl } from "../url.ts";
2
2
  import type { BusinessId, OrganizationId } from "./ids.ts";
3
3
  import { serviceId } from "./ids.ts";
4
- import type { JsonLdNode } from "./node.ts";
4
+ import { type JsonLdNode, listed, reference } from "./node.ts";
5
5
 
6
6
  /**
7
7
  * What a business *does*, for the sites that sell work rather than tickets.
@@ -82,29 +82,23 @@ export interface ServiceInput {
82
82
  }
83
83
 
84
84
  /** An `Audience` node, or nothing. See `ServiceInput.audience`. */
85
- function audienceFor(audienceType: string | undefined): {
86
- readonly audience?: JsonLdNode;
87
- } {
85
+ function audienceFor(audienceType: string | undefined): JsonLdNode | undefined {
88
86
  return audienceType === undefined
89
- ? {}
90
- : { audience: { "@type": "Audience", audienceType } };
87
+ ? undefined
88
+ : { "@type": "Audience", audienceType };
91
89
  }
92
90
 
93
91
  /** `areaServed`, as `Place` nodes. See `ServiceArea`. */
94
- function areasFor(areas: readonly ServiceArea[] | undefined): {
95
- readonly areaServed?: readonly JsonLdNode[];
96
- } {
92
+ function areasFor(
93
+ areas: readonly ServiceArea[] | undefined
94
+ ): readonly JsonLdNode[] | undefined {
97
95
  // Length rather than presence: a service that serves nowhere in particular
98
96
  // says nothing, and an empty list is a claim to have been asked and had no
99
97
  // answer.
100
- if (areas === undefined || areas.length === 0) return {};
101
-
102
- return {
103
- areaServed: areas.map((area) => ({
104
- "@type": area.kind ?? "AdministrativeArea",
105
- name: area.name,
106
- })),
107
- };
98
+ return listed(areas)?.map((area) => ({
99
+ "@type": area.kind ?? "AdministrativeArea",
100
+ name: area.name,
101
+ }));
108
102
  }
109
103
 
110
104
  /**
@@ -118,17 +112,11 @@ export function service(input: ServiceInput): JsonLdNode {
118
112
  "@id": serviceId(input.url),
119
113
  name: input.name,
120
114
  url: input.url,
121
- ...(input.description === undefined
122
- ? {}
123
- : { description: input.description }),
124
- ...(input.serviceType === undefined
125
- ? {}
126
- : { serviceType: input.serviceType }),
127
- ...(input.provider === undefined
128
- ? {}
129
- : { provider: { "@id": input.provider } }),
130
- ...audienceFor(input.audience),
131
- ...areasFor(input.areaServed),
115
+ description: input.description,
116
+ serviceType: input.serviceType,
117
+ provider: reference(input.provider),
118
+ audience: audienceFor(input.audience),
119
+ areaServed: areasFor(input.areaServed),
132
120
  };
133
121
  }
134
122
 
@@ -212,9 +200,7 @@ export function offerCatalog(input: OfferCatalogInput): JsonLdNode {
212
200
  return {
213
201
  "@type": "OfferCatalog",
214
202
  name: input.name,
215
- ...(input.description === undefined
216
- ? {}
217
- : { description: input.description }),
203
+ description: input.description,
218
204
  // Unconditional: this is the node's only link to anything.
219
205
  provider: { "@id": input.provider },
220
206
  itemListElement: input.entries.map((entry) => ({
@@ -224,13 +210,11 @@ export function offerCatalog(input: OfferCatalogInput): JsonLdNode {
224
210
  // The id only where there is a page to hang it on. A fragment
225
211
  // invented for an entry with no page would be an identity for
226
212
  // something nothing else can ever refer to.
227
- ...(entry.url === undefined
228
- ? {}
229
- : { "@id": serviceId(entry.url), url: entry.url }),
213
+ "@id":
214
+ entry.url === undefined ? undefined : serviceId(entry.url),
215
+ url: entry.url,
230
216
  name: entry.name,
231
- ...(entry.description === undefined
232
- ? {}
233
- : { description: entry.description }),
217
+ description: entry.description,
234
218
  },
235
219
  })),
236
220
  };
@@ -227,13 +227,9 @@ export function videoObject(video: VideoInput): JsonLdNode {
227
227
  )
228
228
  ),
229
229
  uploadDate: video.uploadDate,
230
- ...(video.description === undefined
231
- ? {}
232
- : { description: video.description }),
233
- ...(duration === undefined ? {} : { duration }),
234
- ...(video.contentUrl === undefined
235
- ? {}
236
- : { contentUrl: video.contentUrl }),
237
- ...(video.embedUrl === undefined ? {} : { embedUrl: video.embedUrl }),
230
+ description: video.description,
231
+ duration,
232
+ contentUrl: video.contentUrl,
233
+ embedUrl: video.embedUrl,
238
234
  };
239
235
  }
@@ -1,6 +1,6 @@
1
1
  import type { HttpsUrl } from "../url.ts";
2
2
  import { type OrganizationId, websiteId } from "./ids.ts";
3
- import { alternateName, type JsonLdNode } from "./node.ts";
3
+ import { alternateName, type JsonLdNode, reference } from "./node.ts";
4
4
 
5
5
  export interface WebsiteInput {
6
6
  /**
@@ -50,9 +50,7 @@ export function website(site: WebsiteInput): JsonLdNode {
50
50
  "@id": websiteId(site.url),
51
51
  name: site.name,
52
52
  url: site.url,
53
- ...alternateName(site.name, site.alternateName),
54
- ...(site.publisher === undefined
55
- ? {}
56
- : { publisher: { "@id": site.publisher } }),
53
+ alternateName: alternateName(site.name, site.alternateName),
54
+ publisher: reference(site.publisher),
57
55
  };
58
56
  }
@@ -9,32 +9,9 @@ import type { RobotsPolicy } from "./robots.ts";
9
9
  import type { ShareImage } from "./share-image.ts";
10
10
 
11
11
  /**
12
- * What only a consumer can supply: a page's words, and what kind of thing it is.
12
+ * What only a consumer can supply: a page's words, and whether it is an article.
13
13
  */
14
14
 
15
- /**
16
- * What kind of thing a page represents, for `og:type`.
17
- *
18
- * The whole vocabulary Open Graph defines, and a union for the reason
19
- * `RobotsDirective` is one: it is small and closed, and a value outside it is
20
- * not an error anywhere — a scraper that does not recognise `og:type` falls
21
- * back to `website` and renders a perfectly ordinary card. So `"artcile"` costs
22
- * you the article treatment and looks exactly like success.
23
- *
24
- * `website` is the default and the right answer for most pages. `article` is
25
- * the one that earns anything extra: it is what tells a scraper this is a dated
26
- * piece of writing rather than a section of the site.
27
- *
28
- * Reference: <https://ogp.me/#types>
29
- */
30
- export type OpenGraphType =
31
- | "website"
32
- | "article"
33
- | "book"
34
- | "profile"
35
- | `music.${"song" | "album" | "playlist" | "radio_station"}`
36
- | `video.${"movie" | "episode" | "tv_show" | "other"}`;
37
-
38
15
  /**
39
16
  * A page that is a dated piece of writing, said once.
40
17
  *
@@ -48,10 +25,11 @@ export type OpenGraphType =
48
25
  * Supplying this is what emits the `Article` node, `og:type: article` and the
49
26
  * `article:*` properties together. They are three statements of one fact, and a
50
27
  * page that could make them separately is a page that could make two of them.
51
- * That is why `MetaContent.type` cannot be set to `"article"`: saying a page is
52
- * an article without saying when it was published is the half-statement this
53
- * exists to prevent, and it is also the half that carries no benefit — the date
54
- * is the whole reason the type is worth claiming.
28
+ * That is why there is no `type` to set: saying a page is an article without
29
+ * saying when it was published is the half-statement this exists to prevent.
30
+ * Open Graph's other kinds (`book`, `profile`, `video.*`) each come with fields
31
+ * of their own; one would be added the same way, as a field that is the
32
+ * description, with the two made exclusive then.
55
33
  */
56
34
  export interface ArticleContent {
57
35
  /**
@@ -99,34 +77,8 @@ export interface ArticleContent {
99
77
  readonly images?: readonly ImageAsset[];
100
78
  }
101
79
 
102
- /**
103
- * What kind of page this is: an ordinary one, or an article.
104
- *
105
- * A union rather than two optional fields, so the pair cannot both be set. They
106
- * answer the same question — `article` decides `og:type` — and a page that
107
- * supplied both would have one of them silently ignored, which is the shape of
108
- * mistake this folder keeps removing rather than reporting.
109
- *
110
- * Both branches allow the fields to be absent, so an ordinary page writes
111
- * neither and gets `website`.
112
- */
113
- export type PageKind =
114
- | {
115
- readonly type?: Exclude<OpenGraphType, "article">;
116
- readonly article?: never;
117
- }
118
- | {
119
- readonly type?: never;
120
- /**
121
- * This page's publication details, if it is a dated piece of writing.
122
- *
123
- * One field, three outputs — see `ArticleContent`.
124
- */
125
- readonly article: ArticleContent;
126
- };
127
-
128
- /** The fields every page has, whatever kind it is. */
129
- export interface MetaContentBase {
80
+ /** What a page passes to `metaFor`: its words, its picture, and whether it is an article. */
81
+ export interface MetaContent {
130
82
  readonly title: string;
131
83
  /**
132
84
  * Required: a page without a description leaves search engines and every
@@ -172,18 +124,14 @@ export interface MetaContentBase {
172
124
  * and never sees the page they end up on.
173
125
  */
174
126
  readonly jsonLd?: readonly JsonLdNode[];
127
+ /**
128
+ * This page's publication details, if it is a dated piece of writing:
129
+ * `og:type` becomes `article`, with its `article:*` properties and an
130
+ * `Article` node. Without it a page is a `website`. See `ArticleContent`.
131
+ */
132
+ readonly article?: ArticleContent;
175
133
  }
176
134
 
177
- /**
178
- * What a page passes to `metaFor`: its words, its picture, and what kind of
179
- * thing it is.
180
- *
181
- * An intersection rather than one interface because `PageKind` is a union, and
182
- * that union is the whole point — see it for why `type` and `article` cannot
183
- * both be written.
184
- */
185
- export type MetaContent = MetaContentBase & PageKind;
186
-
187
135
  /** Browser-UI tint. A single colour, or one per `prefers-color-scheme`. */
188
136
  export type ThemeColor =
189
137
  | string
package/src/meta/index.ts CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  serializeJsonLd,
38
38
  } from "../jsonld/index.ts";
39
39
  import type { HttpsUrl } from "../url.ts";
40
- import type { MetaContentBase, PageKind, ThemeColor } from "./content.ts";
40
+ import type { MetaContent, ThemeColor } from "./content.ts";
41
41
  import { robotsContent, warnAboutRobots } from "./robots.ts";
42
42
  import {
43
43
  type OgImage,
@@ -47,13 +47,7 @@ import {
47
47
  import { link, type MetaTag, meta, preamble } from "./tag.ts";
48
48
  import { type SiteVerification, verificationTags } from "./verification.ts";
49
49
 
50
- export type {
51
- ArticleContent,
52
- MetaContent,
53
- OpenGraphType,
54
- PageKind,
55
- ThemeColor,
56
- } from "./content.ts";
50
+ export type { ArticleContent, MetaContent, ThemeColor } from "./content.ts";
57
51
  export type { RobotsDirective, RobotsPolicy } from "./robots.ts";
58
52
  export {
59
53
  fitDescription,
@@ -66,15 +60,8 @@ export {
66
60
  export type { MetaTag } from "./tag.ts";
67
61
  export type { SiteVerification } from "./verification.ts";
68
62
 
69
- /**
70
- * Everything the head needs: what the page supplies, plus what lib derives.
71
- *
72
- * `PageKind` is intersected rather than inherited so that `Omit` applies to the
73
- * base alone: omitting a key from a union would flatten it and lose the
74
- * exclusivity that union exists for.
75
- */
76
- export type MetaInput<L extends string> = Omit<MetaContentBase, "image"> &
77
- PageKind &
63
+ /** Everything the head needs: what the page supplies, plus what lib derives. */
64
+ export type MetaInput<L extends string> = Omit<MetaContent, "image"> &
78
65
  MetaInputDerived<L>;
79
66
 
80
67
  interface MetaInputDerived<L extends string> extends ChromeInput {
@@ -90,14 +77,9 @@ interface MetaInputDerived<L extends string> extends ChromeInput {
90
77
  /** The share image, already resolved against the site origin. */
91
78
  readonly image: OgImage;
92
79
  /**
93
- * `ArticleContent.images`, already resolved against the site origin.
94
- *
95
- * A sibling of `article` rather than a field inside it, because `PageKind`
96
- * is a union and `Omit` on a union flattens it — which is the whole reason
97
- * `MetaInput` intersects that union separately instead of omitting through
98
- * it. So the resolved form arrives beside the page's own, exactly as
99
- * `image` does: a page states `ShareImage`, lib hands `buildMeta` an
100
- * `OgImage`.
80
+ * `ArticleContent.images`, already resolved against the site origin: the
81
+ * page states its crops, as it states `image`, and lib hands `buildMeta`
82
+ * their absolute URLs beside them.
101
83
  */
102
84
  readonly articleImages?: readonly HttpsUrl[];
103
85
  /**
@@ -288,16 +270,12 @@ export function buildMeta<L extends string>(input: MetaInput<L>): DocumentTags {
288
270
 
289
271
  // Open Graph
290
272
  //
291
- // The type follows the content rather than being stated beside it: a page
292
- // that described an article and called itself a website would be two claims
293
- // about one thing, and only one of them could be right.
273
+ // The type follows the content rather than being stated beside it: an
274
+ // article is a page that describes one.
294
275
  tags.push(
295
276
  meta({
296
277
  property: "og:type",
297
- content:
298
- input.article === undefined
299
- ? (input.type ?? "website")
300
- : "article",
278
+ content: input.article === undefined ? "website" : "article",
301
279
  })
302
280
  );
303
281
  tags.push(meta({ property: "og:title", content: input.title }));
package/src/project.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { AnalyticsSettings } from "./analytics/index.ts";
2
2
  import type {
3
- LocaleMetaOverride,
4
3
  LocaleNotEnabled,
5
4
  LocalesOf,
6
5
  RoutingConfig,
@@ -131,10 +130,6 @@ export interface ProjectInput<L extends string, Routes> {
131
130
  * reads like the whole truth when it is only a patch.
132
131
  */
133
132
  readonly overrideRouting?: Partial<RoutingConfig<L>>;
134
- /** Restates a locale's label. Its writing direction is not overridable. */
135
- readonly overrideLocaleMeta?: Readonly<
136
- Partial<Record<L, LocaleMetaOverride>>
137
- >;
138
133
  /** Adds to the site's `defaultFonts`, or replaces one by key. */
139
134
  readonly overrideFonts?: Fonts;
140
135
  /**
@@ -237,8 +232,9 @@ export type ProjectChecks<
237
232
  // Against the published locales rather than the site's list: a
238
233
  // segment spelled for a language this deployment does not
239
234
  // publish is a word that appears in no URL it builds. Mapped
240
- // over the keys written, for the reason `overrideLocaleMeta`
241
- // below is.
235
+ // over the keys written: `T` is inferred from the literal, so a
236
+ // stray key just becomes part of `T` and there is no fresh object
237
+ // for excess-property checking to fire on.
242
238
  readonly pageSegmentByLocale?: {
243
239
  readonly [K in StringKeys<
244
240
  NonNullable<T["overrideRouting"]>["pageSegmentByLocale"]
@@ -249,18 +245,8 @@ export type ProjectChecks<
249
245
  T["overrideRoutes"],
250
246
  StringKeys<Routes>
251
247
  >;
252
- // Nested, so that `dir` is rejected inside each locale's entry.
253
- // Declaring the field as `Pick<LocaleMeta, "label">` is not enough:
254
- // `T` is *inferred* from the literal, so a stray `dir` just becomes
255
- // part of `T` and there is no fresh object for excess-property
256
- // checking to fire on.
257
- readonly overrideLocaleMeta?: {
258
- readonly [K in StringKeys<T["overrideLocaleMeta"]>]: K extends Published
259
- ? NoExcessKeys<T["overrideLocaleMeta"][K], keyof LocaleMetaOverride>
260
- : LocaleNotEnabled<K>;
261
- };
262
248
  // The clock's locales must be ones this project publishes, as for
263
- // `overrideLocaleMeta` above. That every one it publishes is there is
249
+ // `pageSegmentByLocale` above. That every one it publishes is there is
264
250
  // checked in `ProjectOverrideChecks`: a conditional on `T` here would make
265
251
  // the constraint circular.
266
252
  readonly formats?: {
@@ -353,19 +353,6 @@ export type SectionPrefixOf<Routes, Overrides> = FirstSegment<
353
353
  EnabledSlugs<Routes, Overrides>
354
354
  >;
355
355
 
356
- /**
357
- * The sections with no page of their own — a prefix that heads a group but is
358
- * not itself a slug anything is published at.
359
- *
360
- * These are the only sections whose link a consumer may choose. Where an index
361
- * page exists it is the answer, and offering a second one would invite a
362
- * heading that points somewhere other than the page it names.
363
- */
364
- export type OrphanSectionPrefixOf<Routes, Overrides> = Exclude<
365
- SectionPrefixOf<Routes, Overrides>,
366
- EnabledSlugs<Routes, Overrides>
367
- >;
368
-
369
356
  /**
370
357
  * The route ids that survive a given override map, computed from literal types.
371
358
  *
package/src/site/api.ts CHANGED
@@ -51,7 +51,6 @@ export interface LlmsOptions<
51
51
  RouteId extends string,
52
52
  L extends string,
53
53
  Prefix extends string = string,
54
- OrphanPrefix extends string = Prefix,
55
54
  > {
56
55
  /**
57
56
  * This route's name and one-line summary, in the file's language.
@@ -88,21 +87,6 @@ export interface LlmsOptions<
88
87
  * comparison that quietly never matches and leaves the default in place.
89
88
  */
90
89
  sectionHeading?(prefix: Prefix, locale: L): string | undefined;
91
- /**
92
- * Where a section with no index page should link.
93
- *
94
- * Separate from the heading because it is an enrichment rather than half of
95
- * one decision: a section can be renamed without being linked, and linked
96
- * without being renamed.
97
- *
98
- * Only sections that *have* no page of their own are offered. Where an index
99
- * page exists it is already the link, and `OrphanPrefix` excludes it — so
100
- * pointing a named family somewhere other than the page it is named after is
101
- * a compile error rather than a choice. Returning `undefined` leaves the
102
- * heading unlinked, which is the right answer when those pages are listed
103
- * nowhere else.
104
- */
105
- sectionLink?(prefix: OrphanPrefix, locale: L): RouteId | undefined;
106
90
  /** The `>` block under the title: what this site is, in a sentence or two. */
107
91
  readonly summary?: string;
108
92
  /**
@@ -132,8 +116,6 @@ export interface Site<
132
116
  * be, and no annotation should have to state it.
133
117
  */
134
118
  Prefix extends string = string,
135
- /** Of those, the ones with no index page of their own. */
136
- OrphanPrefix extends string = Prefix,
137
119
  > {
138
120
  readonly locales: readonly L[];
139
121
  readonly defaultLocale: L;
@@ -356,9 +338,7 @@ export interface Site<
356
338
  * the first time a route is switched off or a slug is retranslated, and
357
339
  * nothing reports it, because nothing reads the file during a build.
358
340
  */
359
- llms(
360
- options?: LlmsOptions<RouteId, L, Prefix, OrphanPrefix>
361
- ): GeneratedFile;
341
+ llms(options?: LlmsOptions<RouteId, L, Prefix>): GeneratedFile;
362
342
 
363
343
  /**
364
344
  * Old URLs that must keep working, resolved — for `siteRoutes()` only.