Skip to content

Improve search engine discoverability and add /build developer page#6

Merged
erskingardner merged 4 commits into
masterfrom
feat/llm-agent-discoverability
Mar 23, 2026
Merged

Improve search engine discoverability and add /build developer page#6
erskingardner merged 4 commits into
masterfrom
feat/llm-agent-discoverability

Conversation

@Datawav

@Datawav Datawav commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix per-page canonical tags (previously all pages reported the root URL as canonical)
  • Add explicit named AI bot directives to robots.txt for 12 major crawlers
  • Add <link rel="alternate"> headers pointing to machine-readable documentation files
  • Add SoftwareApplication JSON-LD schema to homepage alongside existing FAQPage
  • Fix download page schema: operatingSystem as array, downloadUrl array with both TestFlight and APK links, plus codeRepository and license
  • Add CollectionPage + ItemList schema to blog index with XSS-safe serialization
  • Add WebPage + ItemList schema to contribute page
  • Add canonical tags to all remaining pages (download, contribute, privacy-matters, blog index, blog posts)
  • Create /build developer documentation page with Marmot Protocol reference: MDK quick-start, crate structure, Nostr event kinds table, ciphersuite, identity model, client flow, MIP status, known limitations, TypeScript guide, key dependencies, and repository table
  • Add "Build" nav link to header (desktop + mobile) and footer
  • Add /build to XML sitemap
  • Add vercel.json with X-Robots-Tag and Link response headers
  • Update llms.txt with /build page references and promoted "Building with Marmot" section

Test plan

  • npx svelte-check --threshold error passes with 0 errors
  • Visit each page and inspect <head> for correct canonical URL and JSON-LD schemas
  • Verify /build renders with all sections (MDK crates, event kinds, MIP status, etc.)
  • Verify "Build" link appears in header nav, mobile menu, and footer
  • Verify /sitemap.xml includes /build
  • Verify /robots.txt shows named bot blocks
  • Verify /llms.txt references the new /build page
  • Validate JSON-LD with Google Rich Results Test on homepage, download, blog index

Summary by CodeRabbit

  • New Features

    • Added a "Build" nav link and a dedicated Build documentation page; exposed plaintext developer resources at /llms.txt and /llms-full.txt
    • Enhanced blog rendering to serve sanitized HTML for posts
  • Documentation

    • Added canonical links and structured JSON-LD across pages (site, blog, FAQ, build, contribute, download)
  • Chores

    • Updated robots/headers and sitemap to surface LLM-friendly resources and included the Build page; CI workflow and headers configured for site indexing

…page

- Fix per-page canonical tags (was hardcoded to root URL for all pages)
- Add <link rel="alternate"> tags for machine-readable docs globally
- Rewrite robots.txt with explicit named bot directives (GPTBot,
  PerplexityBot, OAI-SearchBot, Google-Extended, and 8 more crawlers)
- Add SoftwareApplication JSON-LD schema to homepage alongside FAQPage
- Fix download page schema: operatingSystem as array, downloadUrl array
  with both TestFlight and APK links, add codeRepository and license
- Add CollectionPage + ItemList schema to blog index with XSS-safe
  JSON serialization matching the pattern used on blog post pages
- Add WebPage + ItemList schema to contribute page
- Add canonical tags to all pages (download, contribute, privacy-matters,
  blog index, individual blog posts)
- Create /build developer page with Marmot Protocol documentation:
  MDK quick-start, crate structure, Nostr event kinds, ciphersuite,
  identity model, client flow, MIP status, known limitations,
  TypeScript guide, key dependencies, and repository table
- Add "Build" link to header nav (desktop + mobile) and footer
- Add /build to XML sitemap
- Add vercel.json with X-Robots-Tag and Link response headers
- Update machine-readable docs index with /build page references
  and promote "Building with Marmot" section
@vercel

vercel Bot commented Feb 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
whitenoise-web Ready Ready Preview, Comment Mar 23, 2026 9:30am

@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown

Walkthrough

Adds a Build docs page and nav links; injects JSON‑LD and canonical/alternate links across many pages; introduces LLM discovery files and expanded robots allowances; updates sitemap and global Link/X-Robots-Tag headers; adds markdown rendering, caching, tests, CI and related config changes.

Changes

Cohort / File(s) Summary
Navigation
src/lib/components/Header.svelte, src/lib/components/Footer.svelte
Added a "Build" navigation item in header (desktop & mobile) and footer; minor import/formatting tweaks.
App shell & LLM discovery
src/app.html, static/llms.txt, static/llms-full.txt, static/robots.txt, vercel.json
Replaced canonical with alternate links to /llms.txt and /llms-full.txt in app shell; added/updated LLM-friendly index files and robots allowances; added global Link and X-Robots-Tag headers.
New Build docs page
src/routes/build/+page.svelte
Added a large static TechArticle page with documentation, examples, metadata, canonical link, and JSON‑LD injection.
SEO / Structured data across pages
src/routes/+page.svelte, src/routes/blog/+page.svelte, src/routes/blog/[naddr]/+page.svelte, src/routes/contribute/+page.svelte, src/routes/download/+page.svelte, src/routes/privacy-matters/+page.svelte, src/routes/+layout.svelte
Reworked JSON‑LD handling: build objects instead of inlined strings, JSON.stringify + escape <, inject via explicit <script type="application/ld+json">; added canonical links and meta descriptions; adjusted SoftwareApplication/download fields.
Blog markdown rendering & caching
src/lib/server/blog-markdown.ts, src/lib/server/blog-markdown.test.ts, src/lib/blog-post-cache.ts, src/lib/blog-post-cache.test.ts, src/lib/nostr.ts, src/routes/blog/[naddr]/+page.server.ts, src/routes/blog/[naddr]/+page.svelte
Introduced sanitized Markdown renderer (renderBlogHtml using marked + sanitize-html) and unit tests; added in-memory blog post cache module + tests and swapped nostr usage to the cache; server load now returns safeHtml.
Blog list & sitemap tests
src/routes/blog/+page.svelte, src/routes/sitemap.xml/+server.ts, src/routes/sitemap.xml/server.test.ts
Added blog CollectionPage JSON‑LD injection and canonical; included /build in sitemap generator; added sitemap tests verifying XML output and escaping.
CI & tooling config
.github/workflows/ci.yml, package.json, biome.json, svelte.config.js, vercel.json
Added CI workflow (Bun), new ci script, adjusted dependencies (replace DOMPurify with sanitize-html), updated Biome ignores and SvelteKit adapter runtime; vercel.json sets global headers.
Robots / LLM docs content
static/llms.txt, static/llms-full.txt
Refined and reorganized LLM overview and developer reference content; added Build and Full Developer Reference links; updated ecosystem/status notes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and clearly summarizes the main changes: improving search engine discoverability through SEO enhancements and adding a new /build developer documentation page.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llm-agent-discoverability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (6)
src/routes/download/+page.svelte (2)

17-20: Consider adding Zapstore URL to downloadUrl array.

The page displays three download options (TestFlight, Zapstore, GitHub APK), but the downloadUrl array only includes two. Adding the Zapstore URL would provide complete structured data for search engines.

♻️ Add Zapstore URL
     "downloadUrl": [
         "https://testflight.apple.com/join/c6Z7PpxC",
+        "https://zapstore.dev/apps/naddr1qq2k7un89ecxzunjv4ejuamgd96x2mn0d9ek2q3qwhtn0s68y3cs98zysa4nxrfzss5g5snhndv35tk5m2sudsr7ltmsxpqqqplqk7t8ewh",
         "https://github.com/marmot-protocol/whitenoise/releases/latest"
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/download/`+page.svelte around lines 17 - 20, Add the missing
Zapstore link to the structured downloadUrl array so all three visible download
options are represented; locate the downloadUrl array in +page.svelte (the
"downloadUrl" JSON key) and insert the Zapstore URL string alongside the
existing TestFlight and GitHub APK entries, ensuring the array remains valid
JSON/JS syntax and is exported/used unchanged by any surrounding code.

32-32: Add XSS-safe JSON escaping for consistency.

Similar to contribute/+page.svelte, this file should use .replace(/</g, '\\u003c') for consistent escaping across all JSON-LD injections, matching the pattern used in blog/+page.svelte.

♻️ Proposed fix
-    {`@html` '<script type="application/ld+json">' + downloadSchema + '</script>'}
+    {`@html` '<script type="application/ld+json">' + downloadSchema.replace(/</g, '\\u003c') + '</script>'}

Note: Since downloadSchema is already stringified at line 4, the .replace() can be applied directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/download/`+page.svelte at line 32, The injected JSON-LD is not
XSS-escaped: update the HTML injection that uses downloadSchema (the {`@html`
'<script type="application/ld+json">' + downloadSchema + '</script>'}
expression) to apply the same safe-escaping used elsewhere by calling
.replace(/</g, '\\u003c') on the already-stringified downloadSchema before
concatenation so the script tag content is escaped consistently.
src/routes/contribute/+page.svelte (1)

56-56: Add XSS-safe JSON escaping for consistency.

Other pages in this PR (e.g., blog/+page.svelte line 43) escape < characters in JSON-LD output using .replace(/</g, '\\u003c'). While the schema here is static and not currently vulnerable, applying consistent escaping prevents future issues if the schema becomes dynamic.

♻️ Proposed fix
-    {`@html` '<script type="application/ld+json">' + JSON.stringify(contributeSchema) + '</script>'}
+    {`@html` '<script type="application/ld+json">' + JSON.stringify(contributeSchema).replace(/</g, '\\u003c') + '</script>'}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/contribute/`+page.svelte at line 56, The JSON-LD injection uses
{`@html` '<script type="application/ld+json">' + JSON.stringify(contributeSchema)
+ '</script>'} without escaping, so make it XSS-safe by replacing `<` in the
serialized JSON (e.g., call JSON.stringify(contributeSchema).replace(/</g,
'\\u003c')) before embedding; update the template that renders contributeSchema
to use the escaped string so the output matches other pages' escaping.
src/routes/blog/[naddr]/+page.svelte (1)

8-8: Unused reactive state variable.

sanitizedContent is declared with $state("") but is never assigned or used in the template. The actual content rendering uses safeHtml (line 113). This appears to be dead code from a previous implementation.

🧹 Remove unused variable
-let sanitizedContent = $state("");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/blog/`[naddr]/+page.svelte at line 8, Remove the dead reactive
state by deleting the unused declaration `let sanitizedContent = $state("");`
(and any now-unused import of `$state` if present); ensure the template
continues to use `safeHtml` for rendering and that no other code references
`sanitizedContent` (if any do, replace them to use `safeHtml` or the appropriate
value).
src/routes/build/+page.svelte (2)

43-43: Avoid raw {@html} for JSON-LD script construction.

Line 43 works today, but string-building a <script> tag via raw HTML is fragile and easier to misuse later. Prefer direct JSON-LD script content with escaped < characters.

Proposed safer pattern
-    {`@html` '<script type="application/ld+json">' + JSON.stringify(buildSchema) + '</script>'}
+    <script type="application/ld+json">
+        {JSON.stringify(buildSchema).replace(/</g, '\\u003c')}
+    </script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/build/`+page.svelte at line 43, Replace the raw {`@html`
'<script...'> construction that injects HTML with a real <script
type="application/ld+json"> element whose text content is the safely-escaped
JSON-LD; specifically stop using the {`@html` ...} injection around the <script>
tag and instead render the JSON via JSON.stringify(buildSchema).replace(/</g,
'\\u003c') (or an equivalent escape) inside the script element so buildSchema is
serialized as text rather than raw HTML.

211-217: Use the project glitch-* palette consistently on this new page.

The new file mixes in yellow-* and cyan-* classes. Please map these to the defined glitch-* palette for consistency with project theming.

As per coding guidelines: "Use custom colors from the glitch-* palette (defined in app.css) for custom color values."

Also applies to: 223-223, 229-229, 235-235, 241-241, 247-247, 283-283, 288-288, 293-293, 315-315, 321-321, 327-327, 333-333, 339-339, 347-347

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/build/`+page.svelte around lines 211 - 217, The rows use utility
classes like the span with "bg-yellow-100 text-yellow-800 ..." and other
instances of "yellow-*" and "cyan-*" palette classes; replace those with the
project's custom palette classes (e.g., "bg-glitch-100 text-glitch-800" or the
appropriate glitch-bg/glitch-text tokens defined in app.css) so every occurrence
of "bg-yellow-*" "text-yellow-*" and "bg-cyan-*" "text-cyan-*" in the table rows
is mapped to the corresponding "glitch-*" classes (match background and text
contrast equivalents) to ensure consistent theming across the component (check
the span elements and any cells that currently reference yellow/cyan utilities).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/routes/blog/`[naddr]/+page.svelte:
- Line 8: Remove the dead reactive state by deleting the unused declaration `let
sanitizedContent = $state("");` (and any now-unused import of `$state` if
present); ensure the template continues to use `safeHtml` for rendering and that
no other code references `sanitizedContent` (if any do, replace them to use
`safeHtml` or the appropriate value).

In `@src/routes/build/`+page.svelte:
- Line 43: Replace the raw {`@html` '<script...'> construction that injects HTML
with a real <script type="application/ld+json"> element whose text content is
the safely-escaped JSON-LD; specifically stop using the {`@html` ...} injection
around the <script> tag and instead render the JSON via
JSON.stringify(buildSchema).replace(/</g, '\\u003c') (or an equivalent escape)
inside the script element so buildSchema is serialized as text rather than raw
HTML.
- Around line 211-217: The rows use utility classes like the span with
"bg-yellow-100 text-yellow-800 ..." and other instances of "yellow-*" and
"cyan-*" palette classes; replace those with the project's custom palette
classes (e.g., "bg-glitch-100 text-glitch-800" or the appropriate
glitch-bg/glitch-text tokens defined in app.css) so every occurrence of
"bg-yellow-*" "text-yellow-*" and "bg-cyan-*" "text-cyan-*" in the table rows is
mapped to the corresponding "glitch-*" classes (match background and text
contrast equivalents) to ensure consistent theming across the component (check
the span elements and any cells that currently reference yellow/cyan utilities).

In `@src/routes/contribute/`+page.svelte:
- Line 56: The JSON-LD injection uses {`@html` '<script
type="application/ld+json">' + JSON.stringify(contributeSchema) + '</script>'}
without escaping, so make it XSS-safe by replacing `<` in the serialized JSON
(e.g., call JSON.stringify(contributeSchema).replace(/</g, '\\u003c')) before
embedding; update the template that renders contributeSchema to use the escaped
string so the output matches other pages' escaping.

In `@src/routes/download/`+page.svelte:
- Around line 17-20: Add the missing Zapstore link to the structured downloadUrl
array so all three visible download options are represented; locate the
downloadUrl array in +page.svelte (the "downloadUrl" JSON key) and insert the
Zapstore URL string alongside the existing TestFlight and GitHub APK entries,
ensuring the array remains valid JSON/JS syntax and is exported/used unchanged
by any surrounding code.
- Line 32: The injected JSON-LD is not XSS-escaped: update the HTML injection
that uses downloadSchema (the {`@html` '<script type="application/ld+json">' +
downloadSchema + '</script>'} expression) to apply the same safe-escaping used
elsewhere by calling .replace(/</g, '\\u003c') on the already-stringified
downloadSchema before concatenation so the script tag content is escaped
consistently.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c273f0 and eb20f30.

📒 Files selected for processing (14)
  • src/app.html
  • src/lib/components/Footer.svelte
  • src/lib/components/Header.svelte
  • src/routes/+page.svelte
  • src/routes/blog/+page.svelte
  • src/routes/blog/[naddr]/+page.svelte
  • src/routes/build/+page.svelte
  • src/routes/contribute/+page.svelte
  • src/routes/download/+page.svelte
  • src/routes/privacy-matters/+page.svelte
  • src/routes/sitemap.xml/+server.ts
  • static/llms.txt
  • static/robots.txt
  • vercel.json

- Add XSS-safe JSON escaping (.replace(/</g, '\u003c')) to JSON-LD in
  download, contribute, and build pages for consistency
- Add missing Zapstore URL to downloadUrl structured data array
- Remove unused sanitizedContent $state variable from blog post page
- Replace yellow-* status badge colors with glitch-* palette on build page

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/routes/build/+page.svelte (1)

247-247: Switch link colors from text-cyan-* to text-glitch-* to match project palette rules.

These links currently use the non-project palette in several places.

🎨 Suggested patch
- <a href="https://github.com/marmot-protocol/marmot" class="text-cyan-600 hover:underline" target="_blank" rel="noopener noreferrer">
+ <a href="https://github.com/marmot-protocol/marmot" class="text-glitch-700 hover:underline" target="_blank" rel="noopener noreferrer">

- <td class="py-3"><a href="https://github.com/openmls/openmls" class="text-cyan-600 hover:underline" target="_blank" rel="noopener noreferrer">github.com/openmls/openmls</a></td>
+ <td class="py-3"><a href="https://github.com/openmls/openmls" class="text-glitch-700 hover:underline" target="_blank" rel="noopener noreferrer">github.com/openmls/openmls</a></td>

- <p class="text-glitch-700 mt-4">All repositories: <a href="https://github.com/marmot-protocol" class="text-cyan-600 hover:underline" target="_blank" rel="noopener noreferrer">github.com/marmot-protocol</a></p>
+ <p class="text-glitch-700 mt-4">All repositories: <a href="https://github.com/marmot-protocol" class="text-glitch-700 hover:underline" target="_blank" rel="noopener noreferrer">github.com/marmot-protocol</a></p>

As per coding guidelines, "Use custom colors from the glitch-* palette (defined in app.css) for custom color values".

Also applies to: 283-283, 288-288, 293-293, 315-315, 321-321, 327-327, 333-333, 339-339, 347-347

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/build/`+page.svelte at line 247, The anchor element linking to
github.com/marmot-protocol/marmot (and the other similar anchor tags noted) uses
the non-project color class "text-cyan-*" — update those class names to the
project palette "text-glitch-*" (e.g., change text-cyan-600 to text-glitch-600),
leaving other classes like hover:underline, target, and rel intact; apply the
same replacement for all occurrences referenced in the file (the <a> elements at
the listed lines) so links use the glitch-* palette.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/routes/build/`+page.svelte:
- Line 247: The anchor element linking to github.com/marmot-protocol/marmot (and
the other similar anchor tags noted) uses the non-project color class
"text-cyan-*" — update those class names to the project palette "text-glitch-*"
(e.g., change text-cyan-600 to text-glitch-600), leaving other classes like
hover:underline, target, and rel intact; apply the same replacement for all
occurrences referenced in the file (the <a> elements at the listed lines) so
links use the glitch-* palette.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb20f30 and c2ee792.

📒 Files selected for processing (4)
  • src/routes/blog/[naddr]/+page.svelte
  • src/routes/build/+page.svelte
  • src/routes/contribute/+page.svelte
  • src/routes/download/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/contribute/+page.svelte

Comment thread src/routes/build/+page.svelte Outdated
Comment thread src/routes/build/+page.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
src/lib/server/blog-markdown.test.ts (1)

22-29: Consider adding a test for data: URL scheme.

While javascript: URLs are tested, data: URLs in links could also be an attack vector. The current allowedSchemes config should block them, but explicit test coverage would confirm this.

💡 Additional test case
it("strips data urls from links", () => {
    const html = renderBlogHtml("[bad](data:text/html,<script>alert(1)</script>)");
    expect(html).not.toContain("data:");
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/server/blog-markdown.test.ts` around lines 22 - 29, Add a test
asserting that data: URLs are stripped by the markdown renderer: add a new test
alongside the existing "strips javascript urls from links" that calls
renderBlogHtml with a markdown link using a data: URL (e.g.
"[bad](data:text/html,<script>...)") and assert the resulting HTML does not
contain "data:" and that allowed links still render; this verifies the
allowedSchemes logic used by renderBlogHtml is blocking data: URLs as intended.
src/routes/blog/[naddr]/+page.svelte (1)

4-4: Prefer const for non-reassigned props destructuring.

Since data is not reassigned within this component, const would be more idiomatic and clearly signals immutability.

♻️ Minor style fix
-let { data }: { data: PageData } = $props();
+const { data }: { data: PageData } = $props();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/blog/`[naddr]/+page.svelte at line 4, The props destructuring uses
"let" for data but data is never reassigned; change the declaration "let { data
}: { data: PageData } = $props()" to use "const" so the destructured prop is
immutable and expresses intent (update the declaration in +page.svelte where {
data } is obtained from $props()).
src/lib/server/blog-markdown.ts (2)

4-7: Global state: consider local configuration.

marked.setOptions() mutates global state, which could affect other code using marked and complicates testing. Consider using a local Marked instance instead.

♻️ Suggested refactor using a local instance
-import { marked } from "marked";
+import { Marked } from "marked";
 import sanitizeHtml from "sanitize-html";

-marked.setOptions({
+const marked = new Marked({
     gfm: true,
     breaks: true,
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/server/blog-markdown.ts` around lines 4 - 7, The code currently calls
marked.setOptions(...) which mutates global state; instead instantiate and use a
local Marked instance: import or reference the Marked constructor, create const
localMarked = new Marked({ gfm: true, breaks: true }) replacing
marked.setOptions, and update places that call marked.parse/marked(...) to call
localMarked.parse(...) (or localMarked.tokens/parse as used) so configuration
stays local to the blog-markdown module and avoids global mutation.

60-63: Explicit async: false option clarifies synchronous behavior.

The type assertion works because marked.parse() defaults to async: false, which returns string synchronously. Instead of relying on the assertion, pass the option explicitly to make the intent clear:

♻️ Refactor suggestion
 export function renderBlogHtml(markdown: string): string {
-    const html = marked.parse(markdown) as string;
+    const html = marked.parse(markdown, { async: false });
     return sanitizeHtml(html, SANITIZE_OPTIONS);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/server/blog-markdown.ts` around lines 60 - 63, The function
renderBlogHtml relies on a type assertion because marked.parse() implicitly uses
async: false; update the call to marked.parse(markdown, { async: false }) (and
remove the "as string" assertion) so the synchronous return is explicit, then
pass the resulting string into sanitizeHtml(html, SANITIZE_OPTIONS) as before;
this clarifies intent and avoids unsafe casting in renderBlogHtml referencing
marked.parse and SANITIZE_OPTIONS.
src/routes/+page.svelte (1)

117-125: Use $state() for reactive state in Svelte 5.

The openFaqIndices variable is reactive state that changes via toggleFaq(). Per coding guidelines, Svelte 5 files should declare reactive state using $state() instead of top-level let declarations.

♻️ Proposed fix to use Svelte 5 runes
-let openFaqIndices: number[] = [];
+let openFaqIndices = $state<number[]>([]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`+page.svelte around lines 117 - 125, Replace the top-level
mutable let openFaqIndices with a Svelte 5 reactive state created by $state()
and stop reassigning the variable directly in toggleFaq; declare openFaqIndices
using $state([...]) and change toggleFaq to read the current value via the state
API and update it via the state's setter/update methods (instead of using
includes/filter or spreading to reassign), keeping references to the existing
identifiers openFaqIndices and toggleFaq so the rest of the component continues
to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/server/blog-markdown.test.ts`:
- Around line 22-29: Add a test asserting that data: URLs are stripped by the
markdown renderer: add a new test alongside the existing "strips javascript urls
from links" that calls renderBlogHtml with a markdown link using a data: URL
(e.g. "[bad](data:text/html,<script>...)") and assert the resulting HTML does
not contain "data:" and that allowed links still render; this verifies the
allowedSchemes logic used by renderBlogHtml is blocking data: URLs as intended.

In `@src/lib/server/blog-markdown.ts`:
- Around line 4-7: The code currently calls marked.setOptions(...) which mutates
global state; instead instantiate and use a local Marked instance: import or
reference the Marked constructor, create const localMarked = new Marked({ gfm:
true, breaks: true }) replacing marked.setOptions, and update places that call
marked.parse/marked(...) to call localMarked.parse(...) (or
localMarked.tokens/parse as used) so configuration stays local to the
blog-markdown module and avoids global mutation.
- Around line 60-63: The function renderBlogHtml relies on a type assertion
because marked.parse() implicitly uses async: false; update the call to
marked.parse(markdown, { async: false }) (and remove the "as string" assertion)
so the synchronous return is explicit, then pass the resulting string into
sanitizeHtml(html, SANITIZE_OPTIONS) as before; this clarifies intent and avoids
unsafe casting in renderBlogHtml referencing marked.parse and SANITIZE_OPTIONS.

In `@src/routes/`+page.svelte:
- Around line 117-125: Replace the top-level mutable let openFaqIndices with a
Svelte 5 reactive state created by $state() and stop reassigning the variable
directly in toggleFaq; declare openFaqIndices using $state([...]) and change
toggleFaq to read the current value via the state API and update it via the
state's setter/update methods (instead of using includes/filter or spreading to
reassign), keeping references to the existing identifiers openFaqIndices and
toggleFaq so the rest of the component continues to work.

In `@src/routes/blog/`[naddr]/+page.svelte:
- Line 4: The props destructuring uses "let" for data but data is never
reassigned; change the declaration "let { data }: { data: PageData } = $props()"
to use "const" so the destructured prop is immutable and expresses intent
(update the declaration in +page.svelte where { data } is obtained from
$props()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 437acf60-8825-40b5-a4db-ec1c66239d7c

📥 Commits

Reviewing files that changed from the base of the PR and between c2ee792 and 4b4d49e.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .github/workflows/ci.yml
  • biome.json
  • package.json
  • src/lib/blog-post-cache.test.ts
  • src/lib/blog-post-cache.ts
  • src/lib/components/Footer.svelte
  • src/lib/nostr.ts
  • src/lib/server/blog-markdown.test.ts
  • src/lib/server/blog-markdown.ts
  • src/routes/+layout.svelte
  • src/routes/+page.svelte
  • src/routes/blog/+page.svelte
  • src/routes/blog/[naddr]/+page.server.ts
  • src/routes/blog/[naddr]/+page.svelte
  • src/routes/blog/[naddr]/page.server.test.ts
  • src/routes/build/+page.svelte
  • src/routes/contribute/+page.svelte
  • src/routes/download/+page.svelte
  • src/routes/privacy-matters/+page.svelte
  • src/routes/sitemap.xml/+server.ts
  • src/routes/sitemap.xml/server.test.ts
  • svelte.config.js
  • vercel.json
✅ Files skipped from review due to trivial changes (5)
  • biome.json
  • .github/workflows/ci.yml
  • vercel.json
  • src/lib/components/Footer.svelte
  • src/routes/build/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/routes/sitemap.xml/+server.ts
  • src/routes/blog/+page.svelte
  • src/routes/privacy-matters/+page.svelte
  • src/routes/download/+page.svelte

@erskingardner
erskingardner merged commit ba59c28 into master Mar 23, 2026
4 checks passed
@erskingardner
erskingardner deleted the feat/llm-agent-discoverability branch March 23, 2026 09:30
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