Rewritten. The original version of this issue said llms.txt lists operations in spec order and so ignores x-nav-order. That was wrong. llms.txt lists resources (tags), not operations, and the Pagefind records don't use insertion order for ranking. I checked by putting x-nav-order into a real spec and diffing the generated files, which come out identical either way. #26 was written against the original description, so apologies to @anisayakmitra-in for the wasted trip. The actual inconsistency is below, and it's in the same function.
The problem
apiResources() in scripts/build-search.mjs builds the resource list for llms.txt by seeding declared tags from spec.tags, then picking up whatever else turns up as it walks the paths:
const tags = new Map();
for (const t of spec.tags ?? []) tags.set(t.name, …);
for (const item of Object.values(spec.paths ?? {})) {
for (const op of Object.values(item)) {
if (op && Array.isArray(op.tags)) for (const t of op.tags) if (!tags.has(t)) tags.set(t, "");
}
}
So undeclared tags land in traversal order. normalize() in lib/openapi.ts sorts them alphabetically after the declared ones:
.sort((a, b) => {
const ia = tagOrder.indexOf(a);
const ib = tagOrder.indexOf(b);
if (ia === -1 && ib === -1) return a.localeCompare(b);
if (ia === -1) return 1;
if (ib === -1) return -1;
return ia - ib;
})
The two disagree whenever a spec has tags that aren't declared at the document root, which is most specs coming out of codegen. On a NestJS-generated spec I get Requests, Customers, Webhooks, Events, Payments, … in llms.txt against Configuration, Customers, Events, … in the sidebar. Same resources, different order, no reason for it.
Low stakes, but it's the kind of drift that makes an LLM reading llms.txt describe the docs in an order nobody sees on the site.
Proposed solution
Mirror normalize()'s ordering in apiResources(): declared tags in document order, everything else alphabetically after them.
Alternatives
The real fix is that build-search.mjs re-parses the spec instead of going through normalize(), which is also why the tag display-name splitter is duplicated there (see #20). One shared implementation would close this whole class of drift. That's a bigger change than this issue needs, since the script is plain .mjs running outside the Next build, but worth noting as the direction.
Context
Came out of reviewing #21. Not a regression from anything, the two orderings have just never been compared.
The problem
apiResources()inscripts/build-search.mjsbuilds the resource list forllms.txtby seeding declared tags fromspec.tags, then picking up whatever else turns up as it walks the paths:So undeclared tags land in traversal order.
normalize()inlib/openapi.tssorts them alphabetically after the declared ones:The two disagree whenever a spec has tags that aren't declared at the document root, which is most specs coming out of codegen. On a NestJS-generated spec I get
Requests, Customers, Webhooks, Events, Payments, …inllms.txtagainstConfiguration, Customers, Events, …in the sidebar. Same resources, different order, no reason for it.Low stakes, but it's the kind of drift that makes an LLM reading
llms.txtdescribe the docs in an order nobody sees on the site.Proposed solution
Mirror
normalize()'s ordering inapiResources(): declared tags in document order, everything else alphabetically after them.Alternatives
The real fix is that
build-search.mjsre-parses the spec instead of going throughnormalize(), which is also why the tag display-name splitter is duplicated there (see #20). One shared implementation would close this whole class of drift. That's a bigger change than this issue needs, since the script is plain.mjsrunning outside the Next build, but worth noting as the direction.Context
Came out of reviewing #21. Not a regression from anything, the two orderings have just never been compared.