Skip to content

πŸ› Duplicate guide slugs resolve by scan order, and the documented remedy is not actionable for the colliding consumerΒ #3979

Description

@PierreBrisorgueil

What β€” when two modules ship a guide with the same filename slug, the public docs API serves an arbitrary winner and the nav lists a duplicate entry. A consumer that adds its own 00-welcome or 01-quickstart collides with the sample guides shipped in modules/home, and has no way to resolve it from its own code.

Why β€” the module already detects the collision but the only remedy it offers is impossible to apply for the party who hits it. That pushes every consumer into a per-project config workaround, and that workaround does not load in the test environment, so it ends up patched in a shared test file instead.

Scope β€” modules/public/services/public.docs.service.js, modules/public/helpers/public.docs.tree.js.

Today compute() warns and picks the last writer:

if (bySlug.has(entry.slug)) {
  logger.warn(`[public/docs] duplicate guide slug "${entry.slug}" β€” later guide wins; rename one to avoid the collision`);
}
bySlug.set(entry.slug, entry);

Two problems:

  1. "rename one" is not actionable for the colliding party. A consumer cannot rename a guide that ships with the framework. The only escape is config.docs.excludeModules, which is discoverable solely by reading config/index.js.
  2. "later wins" is file-scan order, not a policy. loadGuideEntries sorts by numeric prefix then slug, and buildDocsTree keeps BOTH entries β€” so /api/public/docs lists a duplicate while /api/public/docs/:slug.md returns whichever landed last. The two endpoints can disagree.

The knock-on effect is what makes this worth fixing rather than documenting: the config-level workaround is not loaded under NODE_ENV=test, so consumers compensate inside modules/public/tests/public.docs.integration.tests.js β€” a shared file. That is a local edit on framework-owned test code, which conflicts on every subsequent upstream sync.

Suggested direction (either, or both):

  1. Don't serve the sample guides by default. The guides under modules/home are demo content; a real deployment does not want them in its public docs. Flipping the default to excluded, with explicit opt-in for the demo, removes the collision for everyone and needs no per-consumer config.
  2. Give the module a precedence policy. Carry the originating module on each entry and resolve collisions deterministically β€” e.g. application-level guides override framework-provided ones β€” so the outcome does not depend on scan order, and bySlug and the tree always agree.

Whichever is chosen, buildDocsTree and bySlug should be made consistent so the listing and the fetch endpoint cannot disagree, and the resolution should be reachable without the consumer having to discover a config key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions