Skip to content

feat: Add custom JSON-LD structured data support for blog posts - #338

Open
LindsayLiu777 wants to merge 1 commit into
mainfrom
feat/blog-custom-jsonld-support
Open

feat: Add custom JSON-LD structured data support for blog posts#338
LindsayLiu777 wants to merge 1 commit into
mainfrom
feat/blog-custom-jsonld-support

Conversation

@LindsayLiu777

Copy link
Copy Markdown
Contributor

Summary

Allow blog posts to export custom JSON-LD structured data (FAQ, HowTo, etc.) for Google rich snippet eligibility.

Changes

  • Modified blog/[slug]/page.tsx to read optional jsonLd export from MDX files
  • Renders additional JSON-LD script tags when present
  • Added TypeScript type declaration in mdx.d.ts
  • Backward compatible: posts without jsonLd export work unchanged

Usage

In any MDX blog post:

export const jsonLd = [{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [...]
}];

Test plan

  • TypeScript typecheck passes (all 5 packages)
  • All 803 tests pass
  • Verify blog posts without jsonLd export still render correctly
  • Verify blog posts with jsonLd export render the additional script tags

@LindsayLiu777
LindsayLiu777 requested a review from a team as a code owner July 2, 2026 02:41
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@gusye1234 gusye1234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review — PR #338 (custom JSON-LD support)

Reviewed the diff. Clean, well-scoped change. CI green, tests pass, backward compatible.

Looks good:

  • Optional jsonLd export is destructured off the dynamic MDX import — posts without it are unaffected.
  • Array-wrapping (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) is the right normalization — Google accepts a JSON-LD array in a single <script> tag.
  • Type declaration in mdx.d.ts matches the runtime shape.
  • Sits alongside the existing blogPostingJsonLd script without conflict.

Minor nits (non-blocking):

  1. Empty-array/object rendering. {jsonLd && (...)} will still render a <script> tag if a post exports jsonLd = [] or jsonLd = {}. Consider {jsonLd && (Array.isArray(jsonLd) ? jsonLd.length > 0 : true) && (...)} if you want to guard against empty exports. Very minor — probably not worth the extra logic.
  2. Type nit. export const jsonLd: ... | undefined — since this is a const, TS-wise it's always defined at value-time; the undefined union is more about "the module may not export this at all." Works fine as-is, just noting that ? on a namespace/module member is a slightly more idiomatic way to express "optional export" (but TS doesn't support that on *.mdx module declarations, so this is fine).
  3. Validation. No runtime validation that the exported object is well-formed JSON-LD. Since these are authored MDX files (not user input) and injected via dangerouslySetInnerHTML, malformed JSON-LD is a authoring issue, not a security issue — JSON.stringify handles the escaping. Fine to leave as-is.

Assessment: Ready to merge from a code perspective. No blockers.

LindsayLiu777 pushed a commit that referenced this pull request Jul 8, 2026
- Merge custom jsonLd export from PR #338 into blog page renderer
- Add FAQPage schema to ai-agent-orchestration (3 FAQ items)
- Include metadata.image in BlogPosting JSON-LD and OG/Twitter cards
- Extend BlogPost type with optional image field

Co-authored-by: Cursor <cursoragent@cursor.com>
gusye1234 added a commit that referenced this pull request Aug 3, 2026
…(runtime-error auto-rewake followup)

Tag a non-cleanly-ended turn so it's externally distinguishable from a clean
nap/idle (the gap that let Blair's 2026-08-03 stall-kill masquerade as idle for
30min). Pure observability: records + traces the tag; the rewake policy that
consumes it is B2. onTurnEnd behavior byte-for-byte unchanged.

- turn_end event gains `endReason?: "errored"` (STRICT binary can-rewake
  judgement — single literal, not a union, so B2's `=== "errored"` gate can't be
  silently widened) + separate `terminationCause?: "runtime_error" |
  "killed_stalled"` (cause, for B2 branching) + `errorDetail?` free-text.
- Two symmetric marker points keyed on CAUSE, never bare status (a voluntary
  idle-timeout stop also flips status→stopping): `terminate_stalled` effect →
  killed_stalled (Blair's actual case, independent of any death-rattle);
  1556 non-superseded runtime error → runtime_error. Kill marker authoritative:
  a following rattle does not downgrade it.
- Marker cleared at the trailing turn_end AND at the exit teardown (within the
  `=== state` guard) so a hard exit with no turn_end can't leak it onto the next
  turn.
- endReason/terminationCause/errorDetail land in fsm-trace + onFsmTransition.

Tests (810/810 green, typecheck 0): runtime-error tag; clean turn_end untagged
(byte-identical); superseded reset/nap rattle no-tag; 3a marker-leak clear on
hard exit; red-line-6 REAL induced stall (terminate_stalled, no injected error)
→ killed_stalled; voluntary idle-timeout stop flips stopping but stays untagged.

Charter: plans/daemon-runtime-error-rewake.md (Cecilia arch #352 / Claudette
gate #353). Root cause: /Gus/架构#338+#348.
GenerQAQ pushed a commit that referenced this pull request Aug 3, 2026
…(runtime-error auto-rewake followup)

Tag a non-cleanly-ended turn so it's externally distinguishable from a clean
nap/idle (the gap that let Blair's 2026-08-03 stall-kill masquerade as idle for
30min). Pure observability: records + traces the tag; the rewake policy that
consumes it is B2. onTurnEnd behavior byte-for-byte unchanged.

- turn_end event gains `endReason?: "errored"` (STRICT binary can-rewake
  judgement — single literal, not a union, so B2's `=== "errored"` gate can't be
  silently widened) + separate `terminationCause?: "runtime_error" |
  "killed_stalled"` (cause, for B2 branching) + `errorDetail?` free-text.
- Two symmetric marker points keyed on CAUSE, never bare status (a voluntary
  idle-timeout stop also flips status→stopping): `terminate_stalled` effect →
  killed_stalled (Blair's actual case, independent of any death-rattle);
  1556 non-superseded runtime error → runtime_error. Kill marker authoritative:
  a following rattle does not downgrade it.
- Marker cleared at the trailing turn_end AND at the exit teardown (within the
  `=== state` guard) so a hard exit with no turn_end can't leak it onto the next
  turn.
- endReason/terminationCause/errorDetail land in fsm-trace + onFsmTransition.

Tests (810/810 green, typecheck 0): runtime-error tag; clean turn_end untagged
(byte-identical); superseded reset/nap rattle no-tag; 3a marker-leak clear on
hard exit; red-line-6 REAL induced stall (terminate_stalled, no injected error)
→ killed_stalled; voluntary idle-timeout stop flips stopping but stays untagged.

Charter: plans/daemon-runtime-error-rewake.md (Cecilia arch #352 / Claudette
gate #353). Root cause: /Gus/架构#338+#348.
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