Serve one docs corpus to both the public Help Center and the product - #1
Merged
Conversation
Nine miniflare sqlite files under .wrangler/state were committed at some point and churn on every `wrangler dev`, so every branch carries binary noise that has nothing to do with its change. They are per-machine caches; nothing reads them but the local dev server. Co-authored-by: Cursor <cursoragent@cursor.com>
The Help Center is the source of truth, and the product needs the same articles personalized to an event's features and settings. Copying them into the app would fork the corpus on day one, so the personalization lives in the MDX and resolves differently per surface. Authoring: - IfFeature / IfSetting wrap content that only applies when a feature or setting is on, and `app:` links point at product routes. All three compile to inert data-sb-* attributes, so the public page renders every branch and stays fully crawlable — the markers only mean something once something hydrates them. - Article frontmatter gains features / audience / jtbd, which is what lets retrieval filter by entitlement instead of guessing from prose. - Names are checked against a product contract exported from web-api and web-ui-v2, so a marker naming a feature, setting, or route that no longer exists fails the build rather than silently rendering nothing. Hydration is specified rather than reimplemented: MARKER_SPEC.md defines the rules, src/lib/marker-hydration.mjs is the one implementation, and it is vendored into web-ui-v2 against a shared fixture corpus with a checksum. Two copies drifting would show up as the product hiding content the public page shows, which no test in either repo would catch on its own. Retrieval: `npm run build` now also emits dist/_internal/help-index.json — every article chunked by heading with a per-article contentHash and sanitized body HTML — which web-api pulls nightly to embed for Team Lead. It is the whole corpus in one GET, so the Worker gates /_internal/ on a bearer token and answers a wrong or missing one with the same 404 an unknown path gets. Co-authored-by: Cursor <cursoragent@cursor.com>
Written by `npm run taxonomy:tag`, which infers from folder structure and existing frontmatter, so this is mechanical apart from one article. The tags are what make retrieval entitlement-aware: an event without awards should not be handed an awards article, and an organizer should not get the participant-portal instructions written for speakers. 64 articles now name the features they assume; the participant guides carry an explicit audience because their folder alone does not distinguish who is reading. Coverage is deliberately partial. A wrong feature tag hides an article from someone entitled to it, which is worse than an untagged article ranking a little too broadly, so the script only tags what it can infer confidently and the rest stay untagged until an author knows better. sessions/accept-decline.mdx also gets the first real marker — the speaker confirmation step only exists when enable_speaker_acceptance is on — as the end-to-end proof that a marker survives authoring, build, the public page, and the index. Co-authored-by: Cursor <cursoragent@cursor.com>
CI gains three checks, each for a failure that is invisible until it reaches the product: markers naming a feature or setting that no longer exists, the vendored hydrator drifting from the spec, and an article that stops producing retrievable chunks — which reads as Team Lead simply not knowing something. The redirect audit also greps app source for Help Center URLs, and it counted unit-test fixtures and a doc-comment placeholder as broken production redirects. Four of them, indistinguishable in the output from a real 404 after cutover. It now skips test and fixture files, ignores non-ASCII placeholders, and follows the Worker's own trailing-slash 307 instead of failing it. An audit that reports failures nobody can act on is one people stop reading. Verified after deploying: 541/541 URLs reach a live page. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Docs half of the in-product Help Center work. The app half is in
web-ui-v2 #440 and
web-api #3778.
This is already deployed.
learn.sessionboard.comhas been serving thesecommits since tonight (worker version
d8950ccd), so this PR is not a request toship — it is getting production under CI and review, which it has not been.
What it does
The Help Center stays the single source of truth, and the product renders the
same articles personalized to an event's features and settings. Rather than copy
articles into the app — forking the corpus on day one — personalization lives in
the MDX:
IfFeature/IfSettingwrap conditional content andapp:links point atproduct routes. All compile to inert
data-sb-*attributes, so the public pagerenders every branch and stays fully crawlable. Nothing about SEO changes.
features/audience/jtbd, which is what letsretrieval filter by entitlement instead of inferring from prose.
web-ui-v2, so a marker naming something that no longer exists fails the build.
npm run buildalso emitsdist/_internal/help-index.json(226 articles,1,395 chunks, sanitized body HTML, per-article
contentHash), which web-apipulls nightly to embed for Team Lead. The Worker gates
/_internal/on abearer token and answers a bad one with the same 404 an unknown path gets.
Hydration is specified rather than reimplemented:
MARKER_SPEC.mdplus oneimplementation, vendored into web-ui-v2 against a shared fixture corpus with a
checksum. Two copies drifting would show up as the product hiding content the
public page shows — nothing in either repo would catch that alone.
Worth a reviewer's attention
A wrong tag hides an article from someone entitled to it, so the tagger only
emits what it can infer confidently.
sessions/accept-decline). Themachinery is complete; the corpus is not. In-product reading looks like the
public site until authors tag more.
LAUNCH.mdclaiming itdid. Because the Worker answers a bad token with a 404, a missing file and a
wrong secret look identical — Team Lead would have retrieved nothing, silently.
Now a
postbuildhook, and CI fails if any article stops producing chunks.Test plan
vendored-copy checksum, breadcrumbs, build with link validation, redirect
map, index check
page, 0 failures (11 expected fall-throughs for deleted HubSpot articles)
X-Robots-Tagon canonical host,robots.txtsitemap line, GPTBot 403,
help.301s tolearn., article and sitemap 200indexVersion: 1matches web-api'sSUPPORTED_INDEX_VERSIONgsc_health_check.pycleanGET /_internal/help-index.jsonreturns 200 — needs theHELP_INDEX_TOKENvalue, which only exists in web-api's envMade with Cursor