Skip to content

feat: add DocFX API reference site, published alongside the demo - #453

Merged
twcclegg merged 5 commits into
mainfrom
feature/api-docs-site
Sep 24, 2026
Merged

twcclegg merged 5 commits into
mainfrom
feature/api-docs-site

Conversation

@twcclegg

@twcclegg twcclegg commented Sep 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

A generated API reference site for both packable projects, built with DocFX from the XML doc comments they already emit, and themed with the demo's own design tokens so the two sites read as one product rather than two tools bolted together.

  • Published into the same GitHub Pages deployment as the demo, under /docs/ — one deployment, one domain, no separate hosting.
  • Cross-linked: the demo's sidebar gets an "API Docs" entry; the docs home page links back to the demo, GitHub and NuGet, and carries the install command with a copy-to-clipboard button.
  • Articles: docs/api-differences-from-java.md becomes a page rather than a link buried in the README. docfx/build.sh copies it in and rewrites its repo-relative links to GitHub permalinks, since the published site ships only rendered articles.
  • Curated surface: filterConfig.yml drops compiler-generated and [EditorBrowsable(Never)] members. Twelve public-but-internal types — metadata plumbing and prefix-map internals, unreachable from any consumer entry point — are now marked [EditorBrowsable(Never)] too, taking the reference from 43 documented types to 34. Package validation confirms that is not an API-compat break.

Build it with ./docfx/build.sh, never docfx directly: the script restores what docfx's MSBuild pass needs and copies the articles in, so a local run, the preview workflow and the deploy all start from the same state.

One deliberate coupling worth a reviewer's attention: the docs build in deploy-demo.yml is not continue-on-error. A Pages deployment replaces the whole site, so carrying on past a docs failure would publish a wwwroot with no docs/ in it and take /docs down to a 404 rather than leaving it stale. Failing the job keeps the previous deployment serving, demo and docs together — at the cost of a docfx regression also blocking demo-only deploys.

Reviewing it

docs_preview.yml uploads the rendered site as an artifact on every relevant PR — no merge or Pages publish needed. Download api-docs-preview from the run summary, unzip, and serve it with any static server (python3 -m http.server from inside the folder). Opening index.html over file:// won't work; the site fetches its nav and search data over HTTP.

Test plan

  • Eyeball the preview artifact in light and dark mode against the demo
  • On merge, confirm deploy-demo.yml succeeds and /docs/ is live and themed

@codecov

codecov Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.69%. Comparing base (89b5c8d) to head (a50ce56).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #453   +/-   ##
=======================================
  Coverage   87.69%   87.69%           
=======================================
  Files          43       43           
  Lines        3893     3893           
  Branches      993      993           
=======================================
  Hits         3414     3414           
  Misses        277      277           
  Partials      202      202           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@twcclegg
twcclegg force-pushed the feature/api-docs-site branch from be5c931 to af266ce Compare September 5, 2026 19:38
@twcclegg
twcclegg force-pushed the feature/api-docs-site branch 2 times, most recently from c3429eb to fec4f41 Compare September 6, 2026 19:58
@twcclegg
twcclegg force-pushed the feature/api-docs-site branch 2 times, most recently from f12724d to c46df59 Compare September 17, 2026 15:14
Generated from the XML doc comments both packable projects already emit,
and published into the same GitHub Pages deployment as the demo under
/docs/ - one deployment, one domain, no separate hosting.

Build it with docfx/build.sh, never docfx directly. The script copies
docs/*.md into the gitignored docfx/articles/ (docfx's TOC resolver needs
conceptual markdown inside the project tree) and rewrites their
repo-relative links to GitHub permalinks, since the published site ships
only rendered articles. It also restores and builds PhoneNumbers.Extensions
first: docfx loads both csprojs through MSBuild, which needs a restored
project.assets.json under this repo's Central Package Management. Keeping
that in the script rather than in each caller means the preview workflow,
the deploy workflow and a local run all start from the same state, so a
green preview really does predict a green deploy.

filterConfig.yml excludes compiler-generated members and anything marked
[EditorBrowsable(EditorBrowsableState.Never)], so members the library has
deliberately hidden from IntelliSense do not reappear as ordinary API docs.

DocFX's git integration is off entirely (disableGitFeatures,
_disableContribution). build.sh copies articles in from docs/, so no
_gitContribute path could ever produce a working "Edit this page" link for
them - the file it would point at was never committed there. It also
sidesteps libgit2, which is unverified on this repo's arm64 runners.

The docs build in deploy-demo.yml is deliberately NOT continue-on-error: a
Pages deployment replaces the whole site, so carrying on past a docs
failure would publish a wwwroot with no docs/ in it and take /docs down to
a 404 rather than leaving it stale, while README and the demo sidebar both
link there. Failing the job means the previous deployment keeps serving,
demo and docs together.

docs_preview.yml builds the site on every relevant PR and uploads it as a
downloadable artifact, so a reviewer can see the rendered result without
merging or publishing.

assets/brand/ holds the logomark and favicon at the repo root rather than
inside either site, so everything that needs the mark shares one copy.
Ports the demo's design tokens onto DocFX's modern template - warm-neutral
surfaces, orange accent, Space Grotesk/Manrope/JetBrains Mono, light and
dark - so the two sites read as one product rather than two tools bolted
together. The rail is rebuilt from DocFX's horizontal navbar into the
demo's persistent dark sidebar.

Two cascade traps worth knowing about, since both made earlier attempts
silently inert:

- DocFX's bundled highlight.js theme styles the .hljs class directly. A
  single class selector outranks any number of chained type selectors, so
  "pre code, .article pre" never beat it and every --code-bg/--code-text
  here was dead. These rules match .hljs itself.
- That same bundled theme paints the structural token classes
  (subst/function/params/formula) at its own #dcdcdc, a cool grey against
  this theme's warm --code-text, so plain identifiers inside a dark code
  block came out a different temperature from plain text everywhere else.

Syntax highlighting ("Ember") keeps every token on the warm half of the
wheel, but deliberately spread along a ladder - wine, red, orange, gold,
olive - rather than packed into a narrow orange band. Crowded into ~40
degrees of hue the nearest pair sits about 14 dE apart, which at code size
stops reading as highlighting and starts reading as orange text. Every
colour clears 4.5:1 against its own code background and every pair is at
least 20 dE from the others.

The install command carries a copy-to-clipboard button and wraps the way a
shell does. The block is not a flex row: that makes the command its own
box starting after the "$", so a wrapped line hangs under the "d" of
dotnet instead of returning to the prompt column. The button is floated a
hair under one line-height rather than inset with padding-right, so it
costs width only on the line it sits on and a continuation runs on
underneath it; its tap target lives on a centred pseudo-element, because
sizing the button itself would make the float several lines tall and
shorten all of them.

The cards' grid minimum is 260px, not 230: auto-fit packs as many columns
as the minimum allows, so too small a minimum kept three cramped columns
in the article's ~860px content width - and the install command paid for
it, wrapping to four lines. It was counter-intuitively worse at full width
than in a narrow window, which drops to one roomy column.

main.js is imported by the modern template as an ES module. The copy
handler is delegated from the document rather than bound per button,
because the template renders page content client side and per-node
handlers would be lost on the next navigation. A denied clipboard
(insecure origin, permissions policy) leaves the button idle rather than
claiming a copy that did not happen.
Adds an "API Docs" entry to the demo's Resources section. Nav.BaseUri
already carries the GitHub Pages repo-name PathBase, so "{BaseUri}docs/"
resolves correctly from any client-side route.

The glyph is a component rather than inline markup, per the demo's
SVG-icons-as-components rule; IconTests renders every IconBase subclass,
so it is covered without a new test.
…eference

The port made a lot of Java's package-private classes public, so the
generated reference published metadata plumbing and prefix-map internals
as first-class API alongside PhoneNumberUtil and ShortNumberInfo.

Which types those are was worked out by walking the public signature graph
outward from the entry points a consumer actually starts from
(PhoneNumberUtil, ShortNumberInfo, AsYouTypeFormatter, PhoneNumberMatcher,
the three mappers, the two exceptions, LocaleData, and everything in
Extensions). Twelve public types are unreachable from any of them, named
in no consumer-facing doc, and handed out by nothing:

  AreaCodeMap, AreaCodeMapStorageStrategy, DefaultMapStorage,
  FlyweightMapStorage, BuildMetadataFromBin, BuildMetadataFromXml,
  BuildPrefixMapFromBin, CountryCodeToRegionCodeMap, MappingFileProvider,
  MetadataFilter, MetadataManager, PhoneMetadataCollection (+ its Builder)

They get [EditorBrowsable(EditorBrowsableState.Never)], matching what
RegexCache, PhoneRegex and IMetadataLoader already do - but without
[Obsolete], since unlike those three these are all still used internally
and TreatWarningsAsErrors would break the build. docfx's existing
attribute filter picks them up with no filterConfig change, taking the
reference from 43 documented types to 34 with no dangling cross-references
left behind. Package validation confirms this is not an API-compat break.

Deliberately left visible:

- NumberFormat, PhoneMetadata and PhoneNumberDesc are reachable
  (FormatByPattern takes a List<NumberFormat>; the metadata accessors hand
  out the other two), so they stay documented.
- BuildMetadataFromXml is hidden but still public and callable. The
  supported route for custom metadata is PhoneNumberUtil.CreateInstance(
  Stream), which takes a plain Stream and drives the parser internally, so
  no consumer needs to name it.
- LeniencyExtensions is an extension method on the documented Leniency
  enum - the C# stand-in for Java's Leniency.verify - so hiding it would
  leave Leniency looking like it has no verify at all. Reachability
  analysis structurally cannot see extension methods, so this one is a
  judgement call rather than a measurement.

AGENTS.md said CountryCodeToRegionCodeMap.cs is generated and must not be
hand-edited. Nothing regenerates it: its own header still reads "todo make
this file automatically generated", and lib/github-actions-metadata-update.sh
explicitly treats a change to it as hand-written content. Corrected, since
this commit edits it.
@twcclegg
twcclegg force-pushed the feature/api-docs-site branch from 86d84f7 to a824a61 Compare September 17, 2026 22:09
@twcclegg
twcclegg requested a review from wmundev September 17, 2026 22:13

@wmundev wmundev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@twcclegg looks very good, i've tested it locally too and the site renders fine

Comment thread docfx/docfx.json Outdated
"resource": [
{
"//": "Shared with the Blazor demo, which publishes the same three files into its own wwwroot; kept at the repo root so the two sites cannot drift. Mapped to images/ so _appLogoPath and _appFaviconPath below stay site-relative.",
"src": "../assets/brand",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this but i think the demo doesn't references these assets/brand files at all

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — you're right, and the comment was just wrong. The demo has no favicon link at all and draws its rail logo from Components/Icons/, so docfx was the only thing reading those files.

Moved them to docfx/brand/, corrected the comment, and dropped the assets/brand/** path filters from deploy-demo.yml and docs_preview.yml since docfx/** now covers them. No more repo-root assets/ directory.

Comment thread docfx/index.md Outdated

C# port of Google's [libphonenumber](https://github.com/google/libphonenumber) &mdash; parse, format, validate, and geolocate international phone numbers.

<a class="docs-btn" href="xref:PhoneNumbers">

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<a class="docs-btn" href="xref:PhoneNumbers">
<a class="docs-btn" href="api/PhoneNumbers.html">

you can do this so it renders as a button, not as normal link

Image

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — confirmed the cause: docfx replaces an <a> whose href is an xref: with its own generated anchor, which drops the docs-btn class and the arrow SVG.

Took your fix with one tweak: pointed it at api/PhoneNumbers.yml rather than api/PhoneNumbers.html. Both render the button correctly, but the literal .html trips an InvalidFileLink warning since docfx validates file links against source files — the .yml is the real source and docfx rewrites it to .html on build. Clean build is back to 0 warnings.

…enders as a button

Two review comments from @wmundev:

The `assets/brand/` comment claimed the SVGs were shared with the Blazor demo.
They are not — the demo has no favicon link and draws its rail logo from
`Components/Icons/`, so docfx was the only consumer of a repo-root directory
presented as shared. Move them to `docfx/brand/`, correct the comment, and drop
the now-redundant `assets/brand/**` path filters from both workflows, which
`docfx/**` already covers.

The hero link rendered as plain text rather than a button: docfx replaces an
`<a>` whose href is an `xref:` with its own anchor, dropping the class and the
arrow SVG. Point it at the generated `api/PhoneNumbers.yml` instead — docfx
validates that link and rewrites it to `.html`, so the authored markup survives.
The literal `.html` also works but trips an InvalidFileLink warning; the
rationale lives in the frontmatter, which is stripped from the published page.

Verified with a clean `./docfx/build.sh`: 0 warnings, the button keeps its class
and SVG, and the three brand SVGs still publish to `_site/images/`.
@twcclegg
twcclegg merged commit d6a67ec into main Sep 24, 2026
9 checks passed
@twcclegg
twcclegg deleted the feature/api-docs-site branch September 24, 2026 00:02
twcclegg added a commit that referenced this pull request Sep 24, 2026
#453 moved assets/brand/ to docfx/brand/ on the grounds that nothing outside
docfx read it. That was true of main at the time, but this branch is what makes
the demo read it — a Content item in PhoneNumbers.Demo.csproj links the same
three SVGs into wwwroot/, and index.html swaps logo.svg/logo-dark.svg from the
theme toggle. Merging the two as they stood produced two byte-identical copies
and no single source of truth, with no merge conflict to flag it.

Consolidate back onto assets/brand/ as both PRs originally intended: drop
docfx/brand/, point docfx.json's resource src back at ../assets/brand, and
restore the assets/brand/** path filters to deploy-demo.yml and
docs_preview.yml so a change to the marks still rebuilds both sites. Correct
the docfx.json and AGENTS.md comments to describe the arrangement that now
actually exists.

Verified: ./docfx/build.sh is clean (0 warnings) with the three SVGs in
_site/images/ and the hero button intact, and a Release publish of the demo
with PathBase=/libphonenumber-csharp/ lands all three in its wwwroot/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants