Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 139 additions & 4 deletions docs/src/components/starlight/Head.astro

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.

Issue:

We already have an StructuredData component where the schema objects are typed using TypeScript. You can find it here. We should add new schema types there and use that component for consistency.

Original file line number Diff line number Diff line change
@@ -1,10 +1,135 @@
---
/**
* Custom Head component for RocketSim docs
* Includes proper favicon and meta tags matching the main site
*/
import Default from "@astrojs/starlight/components/Head.astro";
import PlausibleAnalytics from "@/components/PlausibleAnalytics.astro";

const baseUrl = "https://www.rocketsim.app";
const { entry } = Astro.props;
const title = entry?.data?.title ?? "RocketSim Docs";
const description = entry?.data?.description ?? "";
const slug = entry?.slug ?? "";
const pageUrl = `${baseUrl}/${slug}`;

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

baseUrl is hard-coded here even though the project already centralizes the canonical site URL in src/config/config.json (and astro.config.ts sets site from it). This creates a second source of truth and makes structured data wrong in preview/staging environments. Prefer using Astro.site (configured via site) or importing @/config/config.json and using config.site.base_url for URL construction.

Copilot uses AI. Check for mistakes.

const breadcrumbItems = [
{ name: "Home", url: baseUrl },
{ name: "Docs", url: `${baseUrl}/docs` },
];
if (title !== "RocketSim Docs" && title !== "404") {
breadcrumbItems.push({ name: title, url: pageUrl });
}

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The PR description says the BreadcrumbList is derived from the URL path hierarchy, but the implementation always emits only Home → Docs → <current title> and ignores intermediate segments (e.g. /docs/features/networking/... will still be a direct child of Docs). Either update the PR description or build breadcrumbs from the actual path/navigation hierarchy so the JSON-LD matches the intended behavior.

Copilot uses AI. Check for mistakes.

const isFaqPage = slug.endsWith("support/faq");

const schemas: Record<string, unknown>[] = [];

if (title !== "RocketSim Docs" && title !== "404") {
schemas.push({
"@context": "https://schema.org",
"@type": "TechArticle",
headline: title,
description,
url: pageUrl,
author: {
"@type": "Organization",
name: "RocketSim",
url: baseUrl,
},
publisher: {
"@type": "Organization",
name: "RocketSim",
url: baseUrl,
logo: `${baseUrl}/images/rocketsim-app-icon.png`,
},
mainEntityOfPage: {
"@type": "WebPage",
"@id": pageUrl,
},
});
}

if (breadcrumbItems.length > 1) {
schemas.push({
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbItems.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
name: item.name,
item: item.url,
})),
});
}

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

BreadcrumbList schema is still emitted on the docs 404 page because breadcrumbItems always contains at least Home and Docs, so breadcrumbItems.length > 1 is always true. Error pages generally shouldn’t publish structured data; consider skipping BreadcrumbList when title === "404" (or based on slug) alongside the TechArticle guard.

Copilot uses AI. Check for mistakes.

const faqEntries = isFaqPage
? [
{
q: "Does RocketSim offer out-of-the App Store distribution?",
a: "Yes. Get in touch via support@rocketsim.app.",
},
{
q: "Are there non-recurring subscriptions?",
a: "Yes, you can consider buying Team Licenses at rocketsim.app/team-insights.",
},
{
q: "Can I reimburse my App Store subscription?",
a: "Yes, RocketSim offers Team Licenses as an alternative at rocketsim.app/team-insights.",
},
{
q: "Do you provide commercial or team licenses?",
a: "Yes, check out Team Licenses at rocketsim.app/team-insights.",
},
{
q: "Can I buy a lifetime RocketSim license?",
a: "No, but you can earn a lifetime license through SwiftLee Weekly's referral program. Join the newsletter and follow the instructions.",
},
{
q: "Why is my video not accepted by App Store Connect?",
a: "Videos are optimized for App Previews following Apple's specifications. Make sure to use a Simulator matching a device from the App Preview specifications.",
},
{
q: "Why wouldn't I just use xcrun simctl?",
a: "RocketSim uses xcrun simctl under the hood but enhances the output with touches, device bezels, and a visual interface for quicker access.",
},
{
q: "Why does RocketSim need screen recording permissions?",
a: "RocketSim is sandboxed and needs screen recording permissions to read Simulator window titles, which it uses to determine the currently active Simulator.",
},
{
q: "Where can I report bugs or feature requests?",
a: "Issues and feature requests are managed on GitHub at github.com/AvdLee/RocketSimApp/issues.",
},
{
q: "How can I get PNG images instead of JPEG?",
a: "Disable App Store Connect (ASC) Optimization. ASC requires JPEG images without alpha layer.",
},
{
q: "Why are my iPad captures upside-down?",
a: "RocketSim cannot detect landscape-left or landscape-right and defaults to one rotation. Rotate your Simulator twice and restart the recording.",
},
{
q: "Can I create transparent captures?",
a: "Yes, disable App Preview Optimized and set your background color to transparent.",
},
{
q: "Network Speed Control isn't working, what can I do?",
a: "Quit Xcode, all Simulators, and RocketSim, then reopen them. Check System Settings → Privacy & Security for pending permissions. On macOS Sequoia, enable the Network Extension in System Settings → General → Login Items & Extensions.",
},
]
: [];

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.

Suggestion:

Having these questions and answers here as a duplicate to the faq in the markdown files is bound to diverge in the future. Maybe we can come up with a way to have the questions and answers declared in one place and then automatically placed in both schema values and the markdown file. I'm not sure how, we could do some research there.


if (isFaqPage && faqEntries.length > 0) {
schemas.push({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqEntries.map((faq) => ({

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The FAQ JSON-LD is populated from a hard-coded list that does not match the actual FAQ page content (src/content/docs/docs/support/faq.md). This can lead to structured data being flagged as misleading by search engines. Consider sourcing the FAQ entries from the same content (e.g., frontmatter data, a shared JSON/data module imported by both the page and Head, or generating from the markdown headings/content) so the JSON-LD stays in sync.

Suggested change
if (isFaqPage && faqEntries.length > 0) {
schemas.push({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqEntries.map((faq) => ({
const faqData = Array.isArray(entry?.data?.faq) ? entry.data.faq : [];
if (isFaqPage && faqData.length > 0) {
schemas.push({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqData.map((faq) => ({

Copilot uses AI. Check for mistakes.
"@type": "Question",
name: faq.q,
acceptedAnswer: {
"@type": "Answer",
text: faq.a,
},
})),
});
}
---

<Default {...Astro.props}><slot /></Default>
Expand All @@ -22,4 +147,14 @@ import PlausibleAnalytics from "@/components/PlausibleAnalytics.astro";
<!-- Additional meta for better SEO -->
<meta name="author" content="RocketSim" />

{
schemas.map((schema) => (
<script
type="application/ld+json"
is:inline
set:html={JSON.stringify(schema)}
/>
))
}
Comment on lines +42 to +55

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

set:html={JSON.stringify(schema)} injects raw JSON into a <script> tag. If any field (e.g. title/description) ever contains </script> or certain < sequences, it can break out of the script context and become an XSS vector. Consider escaping < (commonly replacing it with \u003c in the JSON string) before injecting, or using a safer serialization helper.

Copilot uses AI. Check for mistakes.

<PlausibleAnalytics />