Skip to content

docs: re-adopt global theme; components self-inject CSS#716

Merged
johnnygreco merged 4 commits into
mainfrom
lbliii/devnote-kit-self-styled-css
May 29, 2026
Merged

docs: re-adopt global theme; components self-inject CSS#716
johnnygreco merged 4 commits into
mainfrom
lbliii/devnote-kit-self-styled-css

Conversation

@lbliii
Copy link
Copy Markdown
Contributor

@lbliii lbliii commented May 29, 2026

📋 Summary

Re-adopts the canonical NVIDIA Fern global theme (global-theme: nvidia) the "proper" way, fixing the root cause behind the #715 hotfix revert: css is a theme-owned field, so under a global theme Fern replaces the child repo's css: list with the theme's stylesheets at publish, silently dropping DataDesigner's product CSS and unstyling the Dev Notes. Instead of relying on the theme-owned css: field, each dev-note kit component now injects its own styles via a <style dangerouslySetInnerHTML> tag — component MDX is not theme-owned, so the styling survives the theme merge (the same approach NemoClaw uses).

🔗 Related Issue

Follow-up to #713 (adopt theme) and #715 (hotfix revert).

🔄 Changes

🔧 Changed

  • docs.yml: add global-theme: nvidia; drop the theme-owned footer/layout/colors/theme/favicon/js blocks and the entire css: list; keep the partial logo override, GitHub navbar link, and MDX components.
  • Each kit component now injects its own CSS via <style>: BlogCard/BlogGrid, Authors, NotebookViewer, MetricsTable, TrajectoryViewer, and the badge-icon rule in BadgeLinks.
  • fern.config.json: bump CLI pin to 5.41.1 for global-theme support.
  • fern-published-branch.py + README: drop CSS-file references; document the self-injected-styles pattern.

🗑️ Removed

  • fern/main.css, fern/styles/*.css, the local NVIDIA_{dark,light,symbol}.svg logos, and CustomFooter.tsx — all now provided by the global theme.

🧪 Testing

  • fern check passes (0 errors)
  • Visual preview of Dev Notes index + a post (recommend the Fern preview workflow / fern docs dev)

✅ Checklist

  • Follows commit message conventions
  • Commits are signed off (DCO)
  • Architecture docs updated (README "Branding & styling" section)

Supersedes the #715 hotfix: this keeps the canonical theme while making the dev-note styling resilient to the theme merge.

🤖 Generated with Claude Code

johnnygreco and others added 2 commits May 29, 2026 16:56
Signed-off-by: Johnny Greco <jogreco@nvidia.com>
Re-adopts the canonical NVIDIA Fern global theme (global-theme: nvidia)
and fixes the root cause that forced the #715 hotfix revert: `css` is a
theme-owned field, so under a global theme Fern replaces the child repo's
`css:` list with the theme's stylesheets at publish, silently dropping
DataDesigner's product CSS.

Instead of relying on the (theme-owned) `css:` field, each dev-note kit
component now injects its own styles via a <style dangerouslySetInnerHTML>
tag in its render output. Component MDX is not theme-owned, so the styling
survives the theme merge. This is the same approach NemoClaw uses.

- docs.yml: add global-theme: nvidia; drop theme-owned footer/layout/
  colors/theme/favicon/js and the entire css: block; keep the partial
  logo override, GitHub navbar link, and MDX components.
- Inline each component's CSS as a <style> tag: BlogCard/BlogGrid, Authors,
  NotebookViewer, MetricsTable, TrajectoryViewer, and the badge-icon rule
  in BadgeLinks.
- Remove now-unused fern/main.css, fern/styles/*.css, the local NVIDIA
  logo SVGs, and CustomFooter.tsx (all provided by the theme).
- fern.config.json: bump CLI pin to 5.41.1 for global-theme support.
- fern-published-branch.py + README: drop CSS-file references; document
  the self-injected-styles pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Lawrence Lane <llane@nvidia.com>
@lbliii lbliii requested a review from a team as a code owner May 29, 2026 17:35
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

MkDocs preview: https://52926f5f.dd-docs-preview.pages.dev

Fern preview: https://nvidia-preview-pr-716.docs.buildwithfern.com/nemo/datadesigner

Fern previews include the docs-website version archive with PR changes synced into latest. Notebook tutorials are rendered without execution outputs in previews.

@github-actions
Copy link
Copy Markdown
Contributor

Code Review: PR #716docs: re-adopt global theme; components self-inject CSS

Summary

This PR re-adopts the canonical global-theme: nvidia (originally landed in #713, hotfix-reverted in #715) and fixes the underlying breakage by relocating DataDesigner's product CSS from theme-owned docs.yml css: entries into per-component <style dangerouslySetInnerHTML> tags. It also bumps the Fern CLI pin to 5.41.1, removes locally-vendored NVIDIA logo SVGs and main.css (now provided by the theme), deletes CustomFooter.tsx, and updates fern/README.md + fern-published-branch.py to match.

Net change: +603 / −1726, almost entirely under fern/. No code under data_designer/ is touched.

What's good

  • Root-cause framing is correct. The PR description and inline comments correctly identify that css: is theme-owned, so under global-theme Fern replaces the child repo's list with the theme's at publish time. Moving styles into component MDX (which is not theme-owned) sidesteps the merge cleanly. The reasoning is documented in three places (docs.yml, each component header, fern/README.md's new "Branding & styling" section), so the next maintainer won't reintroduce a css: entry.
  • Clean deletion sweep. main.css, styles/*.css, the three NVIDIA_*.svg logos, and CustomFooter.tsx all go together — these are now handled by the theme. fern/scripts/fern-published-branch.py:47-50 correctly drops the matching styles/*.css paths from FERN_DEVNOTE_SUPPORT_PATHS so downstream branches don't try to copy now-deleted files.
  • Inline CSS strings are byte-for-byte ports. I diffed the new *_CSS constants against their deleted counterparts (fern/styles/authors.css, blog-card.css, metrics-table.css, trajectory-viewer.css, notebook-viewer.css) and the BadgeLinks rule from main.css. The rules are identical, just relocated.
  • Sensible injection points. BlogGrid injects once on the grid wrapper (rather than every BlogCard), and BadgeLinks is leaf-level. Matches the "NemoClaw" pattern referenced in the PR body.
  • README & docstring alignment. fern/README.md's new "Branding & styling" subsection plus the per-component docstrings ("Injected here rather than via docs.yml css: …") form a coherent, discoverable trail.

Findings

1. Duplicate <style> tags on multi-instance pages — worth a follow-up

Each render of Authors, MetricsTable, TrajectoryViewer, BadgeLinks, and NotebookViewer injects the full stylesheet via <style dangerouslySetInnerHTML>. Two of these are mostly safe:

  • BlogGrid (fern/components/BlogCard.tsx:296-303) — wraps the grid, so one <style> per grid.
  • NotebookViewer — typically one per page.

But Authors, MetricsTable, BadgeLinks, and TrajectoryViewer may legitimately render multiple times on a single dev-note (e.g. multiple metrics tables, multiple badge rows, repeated trajectory examples). On those pages each instance emits the full CSS body again — for TrajectoryViewer that's ~150 lines of CSS per occurrence in the DOM.

Functionally the page still renders correctly (later identical rules are no-ops), but it's wasted bytes and noisy DevTools. A cheap fix is to gate injection by a module-level flag or a data-style-injected attribute on <head>, e.g.:

const STYLE_ID = "ds-metrics-table-css";
const StyleOnce = () => (
  typeof document !== "undefined" && document.getElementById(STYLE_ID)
    ? null
    : <style id={STYLE_ID} dangerouslySetInnerHTML={{ __html: METRICS_TABLE_CSS }} />
);

This isn't a blocker for correctness, but I'd flag it as a follow-up — especially since dev-notes are exactly the surface where multi-instance is plausible.

2. Hardcoded #76b900 brand color in component CSS

fern/components/BlogCard.tsx:158 (.blog-card:hover { border-color: #76b900; }) hardcodes NVIDIA green where the global theme presumably exposes var(--accent) or similar. The deleted main.css had var(--nv-color-green). Switching to var(--accent, #76b900) would let the theme drive the hover color. Same value today, but it would survive a future theme palette tweak. Optional polish.

3. dangerouslySetInnerHTML is fine here — worth a one-liner confirming why

Reviewers will see five dangerouslySetInnerHTML usages and reflexively flag them. They are safe because all five are static string literals defined at module scope — no user/MDX content is interpolated. Adding // CSS string literal — no user input inline next to one of them (or in the Branding & styling README section) would defuse future drive-by review concerns.

4. Visual smoke test still unchecked

The PR's own checklist explicitly leaves "Visual preview of Dev Notes index + a post" unchecked, and fern check only validates structure, not rendering. Given this is the second attempt at the same theme migration (#713 broke styling, #715 reverted), I'd want the preview verified before merge: dev-notes index, a post with Authors, a post with MetricsTable, and at minimum one TrajectoryViewer page. Specifically check that:

  • the green NVIDIA accent color, header, and footer come through from the global theme,
  • byline avatars render,
  • best-cell highlighting in metrics tables still works,
  • the colab banner buttons in NotebookViewer aren't blue (the rule overriding link color is preserved, but worth eyeballing).

5. Minor: comment drift risk

The BLOG_CARD_CSS constant lost the original explanatory comments that lived in the .css file (e.g. the rmiz pointer-events block, the .blog-card__media margin reset rationale). The behavior is preserved but the why is gone. BlogCard.tsx:296 and the corresponding rules pointer-events: none and margin: 0 !important look arbitrary now. Re-adding a couple of those /* … */ comments inside the template literal would make the CSS as legible as it was before. Same applies to NOTEBOOK_VIEWER_CSS — the deleted file had useful section headers ("Code block line numbers (Fern structure)…").

Project-conventions check

  • ✅ Scope is fern/ only — no Python source touched, no risk to the data_designer package layering.
  • ✅ License headers preserved on the components that already had them (Authors.tsx, etc.). The deleted CustomFooter.tsx carried LicenseRef-NvidiaProprietary — appropriate, since the theme now owns that surface.
  • fern.config.json version bump (5.24.0 → 5.41.1) is the right place for the CLI pin and matches what global-theme requires.
  • fern-published-branch.py allowlist updated in lockstep — no orphan paths.

Risk assessment

Low–medium. This is docs-only and doesn't touch the namespace packages, but it's the third iteration on a surface that's already broken twice in production-facing form (#713#715#716). Style regressions are visual and won't be caught by fern check or unit tests; the safety net here is the manual preview step that is currently unchecked.

Verdict

Approve with two soft asks before merge:

  1. Run the Fern preview (fern docs dev or the Fern preview workflow) over a dev-note index page and at least one post that exercises Authors, MetricsTable, and TrajectoryViewer. Tick the box in the PR checklist once verified.
  2. Consider the duplicate <style> injection follow-up (Finding docs: adding initial mkdocs structure #1) — fine to land separately, but worth a tracking issue so it doesn't get lost.

The hardcoded #76b900 and the missing CSS comments (Findings #2 and #5) are nits and can ship as-is.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR fixes the root cause of the #715 hotfix revert by re-adopting the global-theme: nvidia Fern global theme the "proper" way: instead of listing product CSS in docs.yml's css: field (which is theme-owned and silently overwritten at publish), each dev-note kit component now injects its own stylesheet via <style dangerouslySetInnerHTML>. A new sync_fern_root_config helper is also added so that patch_devnotes keeps docs.yml and fern.config.json in sync on incremental devnote publishes.

  • docs.yml: adds global-theme: nvidia; drops all theme-owned fields and the entire css: list; keeps partial logo override and GitHub link.
  • Each TSX component: moves CSS from standalone .css files into module-scope string constants injected as <style> tags on render.
  • fern-published-branch.py: introduces sync_fern_root_config and integrates it into patch_devnotes; removes stale CSS paths from FERN_DEVNOTE_SUPPORT_PATHS.

Confidence Score: 5/5

Safe to merge — this is a targeted, well-reasoned docs infrastructure change with no runtime logic changes and a matching test for the new script behaviour.

The CSS migration is mechanical and verified by inspection (old .css files removed, identical rules embedded in constants). The new sync_fern_root_config function correctly preserves the published versions block when overwriting docs.yml, and validate_redirect_targets still runs. The added test exercises the full patch_devnotes path including the versions-preservation logic. dangerouslySetInnerHTML is safe here because every injected string is a static module-scope constant with no runtime interpolation.

No files require special attention.

Important Files Changed

Filename Overview
fern/docs.yml Adds global-theme: nvidia, drops all theme-owned fields and the entire css: list; keeps logo, navbar-links, and MDX components. Clean and correctly commented.
fern/scripts/fern-published-branch.py Adds FERN_ROOT_CONFIG_PATHS, copy_required_path, and sync_fern_root_config; integrates config sync into patch_devnotes; removes deleted CSS paths from FERN_DEVNOTE_SUPPORT_PATHS. Logic is correct: versions block is preserved/normalised before and restored after copying source docs.yml.
fern/components/BlogCard.tsx Moves entire blog-card/grid CSS into BLOG_CARD_CSS constant; BlogGrid injects it once per grid render. Hover border now uses var(--accent, #76b900) (theme-aware) instead of the hard-coded hex.
fern/components/NotebookViewer.tsx Moves all notebook-viewer CSS into NOTEBOOK_VIEWER_CSS constant; injects once per NotebookViewer render. CSS content matches the deleted styles/notebook-viewer.css.
packages/data-designer/tests/docs/test_fern_published_branch.py New test covering patch_devnotes: verifies that sync_fern_root_config copies source docs.yml/fern.config.json, strips theme-owned fields from the published copy, and correctly preserves the full versions block (including historical pinned versions).
fern/fern.config.json Bumps CLI pin to 5.41.1 for global-theme support.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant FPB as fern-published-branch.py
    participant PubBranch as Published Branch (docs-website)
    participant Fern as Fern CLI (global-theme: nvidia)

    Note over Dev,Fern: Full release snapshot (sync-source)
    Dev->>FPB: sync-source --source-root --published-root
    FPB->>PubBranch: Clear + copy entire source tree
    FPB->>PubBranch: Merge/restore historical versions block
    Note over PubBranch: docs.yml has global-theme:nvidia, no css: list
    Note over PubBranch: All TSX components have inline CSS

    Note over Dev,Fern: Incremental devnotes patch (patch-devnotes)
    Dev->>FPB: patch-devnotes --source-root --published-root
    FPB->>FPB: sync_fern_root_config()
    FPB->>PubBranch: Copy source docs.yml + fern.config.json
    FPB->>PubBranch: Restore preserved versions block
    FPB->>PubBranch: Validate redirect targets
    FPB->>PubBranch: Copy FERN_DEVNOTE_SUPPORT_PATHS
    FPB->>PubBranch: Patch latest.yml devnotes nav block

    Note over Dev,Fern: Publish
    Dev->>Fern: fern publish
    Fern->>Fern: Apply global-theme:nvidia
    Note over Fern: Theme css: replaces local css: (intentionally none)
    Fern->>PubBranch: Component style tags survive theme merge
Loading

Reviews (3): Last reviewed commit: "Merge #714 (sync Fern root config during..." | Re-trigger Greptile

- BlogCard hover uses var(--accent, #76b900) so the theme palette drives it.
- Restore the explanatory CSS comments lost in the port (rmiz click-to-zoom
  and media margin-reset rationale; NotebookViewer section headers).
- Note at each injection site that the CSS is a static string literal, so
  dangerouslySetInnerHTML carries no user input.
- README documents why injection is intentionally unconditional (duplicate
  <style> tags are harmless; a render-time guard would risk SSR hydration
  mismatch) and points at React <style precedence> as the future dedupe path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Lawrence Lane <llane@nvidia.com>
@lbliii
Copy link
Copy Markdown
Contributor Author

lbliii commented May 29, 2026

Thanks for the review. Addressed in 9ed86ccf, plus verified on the preview:

#4 — Visual smoke test (the main ask): Confirmed on the Fern preview that the self-injected CSS now loads under the global theme. On /dev-notes/overview the .blog-grid / .blog-card__* rules are inlined; on /retriever-sdg-toolkit the .devnote-authors* rules are present. Still worth a human eyeball on a MetricsTable/TrajectoryViewer post for the accent/avatars/best-cell highlight before merge.

#2 — Hardcoded #76b900: BlogCard hover now uses var(--accent, #76b900), so the theme palette drives it (falls back to NVIDIA green).

#3dangerouslySetInnerHTML rationale: Added {/* static CSS string literal (no user input) — safe to inject as raw HTML */} at every injection site, and documented it in the README "Branding & styling" section.

#5 — Lost CSS comments: Restored the explanatory comments (rmiz click-to-zoom + .blog-card__media margin-reset rationale; NotebookViewer section headers).

#1 — Duplicate <style> on multi-instance pages: Keeping injection unconditional, by design. The two suggested guards are SSR-unsafe here: a module-level flag can omit the style on a later request (module state persists across SSR renders), and a document.getElementById check renders <style> on the server but null on the client during hydration → mismatch. Duplicate identical <style> tags are correctness-safe (browser dedupes the rules), so the cost is only bytes/DevTools noise. Documented this in the README with the real fix path: React's <style precedence> hoisting, once Fern's runtime supports it. Happy to open a tracking issue if you'd prefer.

Incorporates johnnygreco's fix so the devnote publish path syncs the
updated fern/docs.yml (global-theme) and fern/fern.config.json (CLI pin)
to docs-website instead of combining new support files with stale root
config — required for this PR's theme migration to propagate.

# Conflicts:
#	fern/scripts/fern-published-branch.py
@lbliii
Copy link
Copy Markdown
Contributor Author

lbliii commented May 29, 2026

Incorporated #714 (@johnnygreco's "sync Fern root config during devnote publish") via merge — it's a hard dependency for this PR, not just a nice-to-have.

Why it's needed here: the patch-devnotes publish path previously copied dev-note support files (components, assets) onto docs-website but left the root fern/docs.yml / fern/fern.config.json stale. This PR changes both of those (adds global-theme: nvidia, bumps the CLI pin to 5.41.1), so without #714 those root-config changes would never propagate through devnote publishing — which is the CI break #714 was fixing. #714 now syncs root config while preserving the published versions: archive.

What the merge brings in: FERN_ROOT_CONFIG_PATHS, copy_required_path, sync_fern_root_config (called from patch_devnotes), and the regression test test_fern_published_branch.py. The one overlap — FERN_DEVNOTE_SUPPORT_PATHS — was resolved to keep both changes (his root-config sync + my removal of the now-deleted styles/*.css entries).

Validation re-run after merge:

  • pytest packages/data-designer/tests/docs/test_fern_published_branch.py → 1 passed
  • ruff check / ruff format --check → clean
  • update_license_headers.py --check → all 570 files OK
  • fern check → 0 errors

@johnnygreco — since #716 now contains your commits, we can either land #716 and close #714, or merge #714 first and I'll drop the merge here. Your call.

@johnnygreco
Copy link
Copy Markdown
Contributor

@lbliii – I'll close #714 – thanks!

@johnnygreco johnnygreco merged commit ad6a1e4 into main May 29, 2026
61 checks passed
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