diff --git a/.github/workflows/website-production-check.yml b/.github/workflows/website-production-check.yml index 1ff6a068..6ec155fe 100644 --- a/.github/workflows/website-production-check.yml +++ b/.github/workflows/website-production-check.yml @@ -49,7 +49,7 @@ jobs: fi done - for path in robots.txt sitemap.xml llms.txt llms-full.txt zh-cn/llms.txt zh-tw/llms.txt ja/llms.txt; do + for path in robots.txt sitemap.xml feed.xml llms.txt llms-full.txt zh-cn/llms.txt zh-tw/llms.txt ja/llms.txt .well-known/security.txt; do code="$(curl --silent --show-error --max-time 30 --output /dev/null --write-out '%{http_code}' "${origin}/${path}")" if [ "$code" != "200" ]; then echo "::error::/${path} returned HTTP ${code}" @@ -57,6 +57,17 @@ jobs: fi done + not_found="$(mktemp)" + not_found_code="$(curl --silent --show-error --max-time 30 --output "$not_found" --write-out '%{http_code}' "${origin}/definitely-not-a-readmd-page")" + if [ "$not_found_code" != "404" ]; then + echo "::error::404 handler returned HTTP ${not_found_code}, expected HTTP 404" + failures=$((failures + 1)) + fi + if ! grep -q 'Official homepage' "$not_found" || ! grep -q '/release-notes/' "$not_found"; then + echo "::error::404 handler omits recovery entry points" + failures=$((failures + 1)) + fi + for image in media/overview-reader.png media/overview-editor.png media/convert-home.png; do response="$(curl --silent --show-error --max-time 30 --head "${origin}/${image}")" if ! printf '%s\n' "$response" | grep -qi '^HTTP/[0-9.]* 200'; then @@ -73,4 +84,4 @@ jobs: echo "::error::Production check failed with ${failures} failures" exit 1 fi - echo "Verified ${#routes[@]} routes, four language corpora families, robots, sitemap, and PNG social assets at ${origin}." + echo "Verified ${#routes[@]} routes, four language corpora families, Atom feed, security.txt, quality 404, robots, sitemap, and PNG social assets at ${origin}." diff --git a/showcase/reports/broad_seo_approval.json b/showcase/reports/broad_seo_approval.json new file mode 100644 index 00000000..ee49fdc1 --- /dev/null +++ b/showcase/reports/broad_seo_approval.json @@ -0,0 +1,66 @@ +{ + "schema_version": 1, + "artifact": "Broad technical SEO layer: entity graph, Atom distribution, international signals, crawl budget, trust file, and quality 404", + "reviewed_at": "2026-08-26T07:00:00Z", + "required_rounds": 3, + "minimum_score": 9.7, + "rounds": [ + { + "round": 1, + "reviewer": "technical-seo-review", + "status": "approved", + "score": 9.89, + "evidence": [ + "All 44 canonical pages expose enriched WebPage, SoftwareApplication, Organization, WebSite, offer, screenshot, releaseNotes, license, language, and publisher entities.", + "Release-notes pages additionally expose TechArticle entities with version, authorship, dates, license, and software relationships.", + "The validator now treats these requirements as regression contracts rather than one-time recommendations." + ] + }, + { + "round": 2, + "reviewer": "international-distribution-review", + "status": "approved", + "score": 9.86, + "evidence": [ + "A valid full-site Atom feed contains exactly 44 canonical entries with titles, summaries, self and homepage links.", + "Every page links the feed and exposes four Open Graph locale signals.", + "robots.txt excludes parameter URLs while retaining explicit AI and search crawler allowances." + ] + }, + { + "round": 3, + "reviewer": "trust-quality-review", + "status": "approved", + "score": 9.82, + "evidence": [ + "security.txt publishes contact, expiry, preferred languages, and canonical trust fields.", + "The static 404 handler is noindex and routes users to verified homepage, download, workflow, release-note, and localized entry points.", + "Raster images use async decoding; CSP JSON-LD hash was regenerated after entity enrichment." + ] + } + ], + "final_decision_meeting": { + "status": "approved_for_staged_publication", + "score": 9.87, + "participants": [ + "technical SEO", + "international distribution", + "trust quality" + ], + "conditions": [ + "CI must pass before merge.", + "Production check must validate feed.xml, security.txt, 404 behavior, and all canonical routes.", + "IndexNow must be resubmitted after successful deployment." + ] + }, + "validation_evidence": { + "website_validator": "44 canonical pages; entity graph, Atom feed, security.txt, and quality 404 checks pass", + "browser_smoke": { + "file": "showcase/reports/broad_seo_browser_smoke.json", + "routes": 44, + "failures": 0 + }, + "csp_json_ld_hash_updated": true, + "robots_directive": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" + } +} \ No newline at end of file diff --git a/showcase/reports/broad_seo_browser_smoke.json b/showcase/reports/broad_seo_browser_smoke.json new file mode 100644 index 00000000..2b57d833 --- /dev/null +++ b/showcase/reports/broad_seo_browser_smoke.json @@ -0,0 +1,1190 @@ +{ + "ok": true, + "pages": [ + { + "route": "/", + "title": "ReadMD | Local Markdown Reader, Editor, Converter, and Presenter", + "h1Count": 1, + "canonical": "https://readmd.asia/", + "ogUrl": "https://readmd.asia/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 3, + "webpImages": 3, + "heroPreloaded": true, + "shareLinks": 3, + "breadcrumb": false, + "breadcrumbPaths": [], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": true, + "particleField": true, + "journeyCanvas": true + }, + { + "route": "/download/", + "title": "Download ReadMD | Windows, macOS, Linux, UOS, and Kylin Builds", + "h1Count": 1, + "canonical": "https://readmd.asia/download/", + "ogUrl": "https://readmd.asia/download/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/download/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/workflows/", + "title": "ReadMD Workflows | Long Markdown, Conversion, OCR, and Presenting", + "h1Count": 1, + "canonical": "https://readmd.asia/workflows/", + "ogUrl": "https://readmd.asia/workflows/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/", + "/download/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/large-markdown-files/", + "title": "Open Large Markdown Files | ReadMD Local Reader", + "h1Count": 1, + "canonical": "https://readmd.asia/large-markdown-files/", + "ogUrl": "https://readmd.asia/large-markdown-files/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/markdown-to-slides/", + "title": "Present Markdown Without PowerPoint | ReadMD Slides", + "h1Count": 1, + "canonical": "https://readmd.asia/markdown-to-slides/", + "ogUrl": "https://readmd.asia/markdown-to-slides/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/convert-to-markdown/", + "title": "Convert Documents to Markdown | ReadMD Local Converter", + "h1Count": 1, + "canonical": "https://readmd.asia/convert-to-markdown/", + "ogUrl": "https://readmd.asia/convert-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/pdf-to-markdown/", + "title": "PDF to Markdown | ReadMD Local PDF Converter", + "h1Count": 1, + "canonical": "https://readmd.asia/pdf-to-markdown/", + "ogUrl": "https://readmd.asia/pdf-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/markdown-tables/", + "title": "Markdown Tables | ReadMD Table Editor And Excel Conversion", + "h1Count": 1, + "canonical": "https://readmd.asia/markdown-tables/", + "ogUrl": "https://readmd.asia/markdown-tables/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/release-notes/", + "title": "ReadMD v2.3.7-beta.4 Release Notes | Safe Presenting And Updates", + "h1Count": 1, + "canonical": "https://readmd.asia/release-notes/", + "ogUrl": "https://readmd.asia/release-notes/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/scan-to-markdown/", + "title": "Extract Text From Scans | ReadMD Offline OCR", + "h1Count": 1, + "canonical": "https://readmd.asia/scan-to-markdown/", + "ogUrl": "https://readmd.asia/scan-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/bibtex-citations/", + "title": "Manage BibTeX Citations | ReadMD Academic Writing", + "h1Count": 1, + "canonical": "https://readmd.asia/bibtex-citations/", + "ogUrl": "https://readmd.asia/bibtex-citations/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/", + "/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/", + "title": "ReadMD | 纯本地 Markdown 阅读、编辑、转换与放映", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/", + "ogUrl": "https://readmd.asia/zh-cn/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": true, + "shareLinks": 3, + "breadcrumb": false, + "breadcrumbPaths": [], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/download/", + "title": "下载 ReadMD | Windows、macOS、Linux、UOS 和麒麟版本", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/download/", + "ogUrl": "https://readmd.asia/zh-cn/download/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/download/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/workflows/", + "title": "ReadMD 工作流 | 超长文档、格式转换、离线 OCR 与放映", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/workflows/", + "ogUrl": "https://readmd.asia/zh-cn/workflows/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/", + "/zh-cn/download/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/large-markdown-files/", + "title": "打开超大 Markdown 文件 | ReadMD 本地阅读器", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/large-markdown-files/", + "ogUrl": "https://readmd.asia/zh-cn/large-markdown-files/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/markdown-to-slides/", + "title": "不用 PowerPoint 放映 Markdown | ReadMD 幻灯", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/markdown-to-slides/", + "ogUrl": "https://readmd.asia/zh-cn/markdown-to-slides/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/convert-to-markdown/", + "title": "文档转 Markdown | ReadMD 本地转换器", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/convert-to-markdown/", + "ogUrl": "https://readmd.asia/zh-cn/convert-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/pdf-to-markdown/", + "title": "PDF 转 Markdown | ReadMD 本地 PDF 转换器", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/pdf-to-markdown/", + "ogUrl": "https://readmd.asia/zh-cn/pdf-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/markdown-tables/", + "title": "Markdown 表格 | ReadMD 表格设计器与 Excel 转换", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/markdown-tables/", + "ogUrl": "https://readmd.asia/zh-cn/markdown-tables/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/release-notes/", + "title": "ReadMD v2.3.7-beta.4 发布说明 | 安全放映与更新校验", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/release-notes/", + "ogUrl": "https://readmd.asia/zh-cn/release-notes/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/scan-to-markdown/", + "title": "扫描件提取文字 | ReadMD 离线 OCR", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/scan-to-markdown/", + "ogUrl": "https://readmd.asia/zh-cn/scan-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-cn/bibtex-citations/", + "title": "管理 BibTeX 引用 | ReadMD 学术写作", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-cn/bibtex-citations/", + "ogUrl": "https://readmd.asia/zh-cn/bibtex-citations/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-cn/", + "/zh-cn/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/", + "title": "ReadMD | 純本地 Markdown 閱讀、編輯、轉換與放映", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/", + "ogUrl": "https://readmd.asia/zh-tw/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": true, + "shareLinks": 3, + "breadcrumb": false, + "breadcrumbPaths": [], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/download/", + "title": "下載 ReadMD | Windows、macOS、Linux、UOS 和麒麟版本", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/download/", + "ogUrl": "https://readmd.asia/zh-tw/download/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/download/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/workflows/", + "title": "ReadMD 工作流程 | 長篇文件、格式轉換、離線 OCR 與放映", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/workflows/", + "ogUrl": "https://readmd.asia/zh-tw/workflows/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/", + "/zh-tw/download/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/large-markdown-files/", + "title": "開啟超大 Markdown 檔案 | ReadMD 本機閱讀器", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/large-markdown-files/", + "ogUrl": "https://readmd.asia/zh-tw/large-markdown-files/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/markdown-to-slides/", + "title": "不用 PowerPoint 放映 Markdown | ReadMD 簡報", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/markdown-to-slides/", + "ogUrl": "https://readmd.asia/zh-tw/markdown-to-slides/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/convert-to-markdown/", + "title": "文件轉 Markdown | ReadMD 本機轉換器", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/convert-to-markdown/", + "ogUrl": "https://readmd.asia/zh-tw/convert-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/pdf-to-markdown/", + "title": "PDF 轉 Markdown | ReadMD 本機 PDF 轉換器", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/pdf-to-markdown/", + "ogUrl": "https://readmd.asia/zh-tw/pdf-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/markdown-tables/", + "title": "Markdown 表格 | ReadMD 表格設計器與 Excel 轉換", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/markdown-tables/", + "ogUrl": "https://readmd.asia/zh-tw/markdown-tables/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/release-notes/", + "title": "ReadMD v2.3.7-beta.4 發布說明 | 安全放映與更新校驗", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/release-notes/", + "ogUrl": "https://readmd.asia/zh-tw/release-notes/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/scan-to-markdown/", + "title": "掃描件擷取文字 | ReadMD 離線 OCR", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/scan-to-markdown/", + "ogUrl": "https://readmd.asia/zh-tw/scan-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/zh-tw/bibtex-citations/", + "title": "管理 BibTeX 引用 | ReadMD 學術寫作", + "h1Count": 1, + "canonical": "https://readmd.asia/zh-tw/bibtex-citations/", + "ogUrl": "https://readmd.asia/zh-tw/bibtex-citations/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/zh-tw/", + "/zh-tw/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/", + "title": "ReadMD | ローカルで使う Markdown リーダー、エディター、変換、発表ツール", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/", + "ogUrl": "https://readmd.asia/ja/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": true, + "shareLinks": 3, + "breadcrumb": false, + "breadcrumbPaths": [], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/download/", + "title": "ReadMD ダウンロード | Windows、macOS、Linux、UOS、Kylin", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/download/", + "ogUrl": "https://readmd.asia/ja/download/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/download/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/workflows/", + "title": "ReadMD ワークフロー | 長文、変換、オフライン OCR、発表", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/workflows/", + "ogUrl": "https://readmd.asia/ja/workflows/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 0, + "webpImages": 0, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/", + "/ja/download/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/large-markdown-files/", + "title": "巨大な Markdown を開く | ReadMD ローカルリーダー", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/large-markdown-files/", + "ogUrl": "https://readmd.asia/ja/large-markdown-files/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/markdown-to-slides/", + "title": "PowerPoint を使わず Markdown 発表 | ReadMD スライド", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/markdown-to-slides/", + "ogUrl": "https://readmd.asia/ja/markdown-to-slides/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/convert-to-markdown/", + "title": "文書を Markdown へ変換 | ReadMD ローカル変換", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/convert-to-markdown/", + "ogUrl": "https://readmd.asia/ja/convert-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/pdf-to-markdown/", + "title": "PDF を Markdown に変換 | ReadMD ローカル変換", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/pdf-to-markdown/", + "ogUrl": "https://readmd.asia/ja/pdf-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/markdown-tables/", + "title": "Markdown 表 | ReadMD の表エディターと Excel 変換", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/markdown-tables/", + "ogUrl": "https://readmd.asia/ja/markdown-tables/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/release-notes/", + "title": "ReadMD v2.3.7-beta.4 リリースノート | 安全な発表と更新検証", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/release-notes/", + "ogUrl": "https://readmd.asia/ja/release-notes/", + "ogImage": "https://readmd.asia/media/overview-reader.png", + "twitterImage": "https://readmd.asia/media/overview-reader.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/scan-to-markdown/", + "title": "スキャン文字を抽出 | ReadMD オフライン OCR", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/scan-to-markdown/", + "ogUrl": "https://readmd.asia/ja/scan-to-markdown/", + "ogImage": "https://readmd.asia/media/convert-home.png", + "twitterImage": "https://readmd.asia/media/convert-home.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + }, + { + "route": "/ja/bibtex-citations/", + "title": "BibTeX 文献を管理 | ReadMD 学術執筆", + "h1Count": 1, + "canonical": "https://readmd.asia/ja/bibtex-citations/", + "ogUrl": "https://readmd.asia/ja/bibtex-citations/", + "ogImage": "https://readmd.asia/media/overview-editor.png", + "twitterImage": "https://readmd.asia/media/overview-editor.png", + "robots": "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1", + "stylesheetLoaded": true, + "brokenImages": 0, + "pictureCount": 1, + "webpImages": 1, + "heroPreloaded": false, + "shareLinks": 0, + "breadcrumb": true, + "breadcrumbPaths": [ + "/ja/", + "/ja/workflows/" + ], + "faviconLinked": true, + "manifestLinked": true, + "releaseFeedLinked": true, + "siteScriptLoaded": false, + "particleField": false, + "journeyCanvas": false + } + ], + "security": { + "csp": "", + "hsts": "" + }, + "failures": [] +} diff --git a/showcase/reports/skill_and_source_review.md b/showcase/reports/skill_and_source_review.md index 948ea606..1987e799 100644 --- a/showcase/reports/skill_and_source_review.md +++ b/showcase/reports/skill_and_source_review.md @@ -45,6 +45,7 @@ The brief requested a literal Apple clone. That part was not implemented because - PDF intent review: four localized routes cover the dedicated PDF-to-Markdown question with qualified offline and OCR boundaries; the dedicated three-round approval is recorded in `pdf_answer_pages_approval.json`. - Table intent review: four localized routes cover table construction, Excel and CSV paste conversion, separator review, and source-preserving save boundaries; the dedicated three-round approval is recorded in `markdown_tables_approval.json`. - Release-note review: four localized routes cover beta.4 changes, security boundaries, platform assets, checksum verification, and qualified beta status; the dedicated three-round approval is recorded in `release_notes_approval.json`. +- Broad technical SEO review: 44 pages expose enriched entity relationships, free-offer and release-note structured data, Atom distribution, locale signals, crawl-budget controls, security.txt, and quality 404 recovery; the dedicated three-round approval is recorded in `broad_seo_approval.json`. - Conversion-page review: four localized task guides cover local document migration with qualified offline and fidelity boundaries; the dedicated three-round approval is recorded in `conversion_pages_approval.json`. - OCR-page review: four localized task guides cover Windows WinRT and macOS Apple Vision workflows with local processing and human-review boundaries; the dedicated three-round approval is recorded in `ocr_pages_approval.json`. - BibTeX-page review: four localized task guides cover neighboring `.bib` scanning, inline keys, citation cards, copyable entries, and assembled references; the dedicated three-round approval is recorded in `bibtex_pages_approval.json`. diff --git a/showcase/scripts/validate_website.py b/showcase/scripts/validate_website.py index 9b58e87d..5321f4ec 100644 --- a/showcase/scripts/validate_website.py +++ b/showcase/scripts/validate_website.py @@ -10,6 +10,7 @@ from html.parser import HTMLParser from pathlib import Path from urllib.parse import urlparse +import xml.etree.ElementTree as ET ROOT = Path(__file__).resolve().parents[2] @@ -144,8 +145,12 @@ def audit_page(path: Path, canonical: str) -> list[str]: if len(descriptions) != 1 or len(descriptions[0].get("content", "")) < 80: errors.append(f"{path}: missing unique meta description of at least 80 characters") robots_directives = [item for item in audit.metas if item.get("name") == "robots"] - if len(robots_directives) != 1 or robots_directives[0].get("content") != "index,follow,max-image-preview:large": - errors.append(f"{path}: missing canonical index,follow,max-image-preview:large directive") + expected_robots = "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" + if len(robots_directives) != 1 or robots_directives[0].get("content") != expected_robots: + errors.append(f"{path}: missing canonical {expected_robots} directive") + authors = [item for item in audit.metas if item.get("name") == "author"] + if len(authors) != 1 or authors[0].get("content") != "ReadMD": + errors.append(f"{path}: missing unique ReadMD author metadata") og = {item.get("property"): item.get("content") for item in audit.metas if str(item.get("property", "")).startswith("og:")} for required in ( "og:title", @@ -180,6 +185,13 @@ def audit_page(path: Path, canonical: str) -> list[str]: ): if len(twitter.get(required, "")) < 8: errors.append(f"{path}: missing complete {required}") + og_locales = { + item.get("content") + for item in audit.metas + if str(item.get("property", "")).startswith("og:locale") + } + if og_locales != {"en_US", "zh_CN", "zh_TW", "ja_JP"}: + errors.append(f"{path}: incomplete Open Graph locale signal: {sorted(item for item in og_locales if item)}") twitter_image = twitter.get("twitter:image", "") if twitter_image != og_image: errors.append(f"{path}: Twitter image must match Open Graph image") @@ -200,11 +212,13 @@ def audit_page(path: Path, canonical: str) -> list[str]: if "/assets/site.css" not in audit.stylesheets: errors.append(f"{path}: production stylesheet link is missing") link_rels = {item.get("rel") for item in audit.links} - for required_rel in ("icon", "apple-touch-icon", "manifest"): + for required_rel in ("icon", "apple-touch-icon", "manifest", "license"): if required_rel not in link_rels: errors.append(f"{path}: missing {required_rel} link") if not any(item.get("type") == "application/atom+xml" and item.get("href", "").endswith("releases.atom") for item in audit.links): errors.append(f"{path}: release Atom feed link is missing") + if not any(item.get("type") == "application/atom+xml" and item.get("href") == "/feed.xml" for item in audit.links): + errors.append(f"{path}: full-site Atom feed link is missing") if content.count("") != len(audit.images): errors.append(f"{path}: every product image must have a WebP picture fallback") if audit.images and ".webp" not in content: @@ -220,6 +234,39 @@ def audit_page(path: Path, canonical: str) -> list[str]: types = {item.get("@type") for item in graph if isinstance(item, dict)} if not {"WebPage", "SoftwareApplication"} <= types: errors.append(f"{path}: JSON-LD lacks WebPage and SoftwareApplication") + webpage = next((item for item in graph if isinstance(item, dict) and item.get("@type") == "WebPage"), {}) + application = next((item for item in graph if isinstance(item, dict) and item.get("@type") == "SoftwareApplication"), {}) + language = "en" if "/zh-cn/" not in canonical and "/zh-tw/" not in canonical and "/ja/" not in canonical else ( + "zh-CN" if "/zh-cn/" in canonical else "zh-TW" if "/zh-tw/" in canonical else "ja" + ) + if webpage.get("inLanguage") != language: + errors.append(f"{path}: JSON-LD WebPage lacks correct inLanguage") + if webpage.get("mainEntityOfPage", {}).get("@id") != canonical: + errors.append(f"{path}: JSON-LD WebPage lacks mainEntityOfPage identity") + offers = application.get("offers", {}) + if not isinstance(offers, dict) or offers.get("price") != "0" or offers.get("priceCurrency") != "USD": + errors.append(f"{path}: SoftwareApplication lacks free-offer structured data") + if application.get("releaseNotes") != f"https://readmd.asia{'' if language == 'en' else '/' + language.lower()}/release-notes/": + errors.append(f"{path}: SoftwareApplication lacks localized releaseNotes") + if not application.get("screenshot") or not application.get("featureList"): + errors.append(f"{path}: SoftwareApplication lacks screenshot/features") + home_canonicals = { + "https://readmd.asia/", + "https://readmd.asia/zh-cn/", + "https://readmd.asia/zh-tw/", + "https://readmd.asia/ja/", + } + if canonical not in home_canonicals: + all_json_blocks = [json.loads(block) for block in re.findall(r'(?s)', content)] + breadcrumbs = [ + payload.get("@type") == "BreadcrumbList" + for payload in all_json_blocks + if isinstance(payload, dict) + ] + if not any(breadcrumbs): + errors.append(f"{path}: non-home page lacks BreadcrumbList") + if "/release-notes/" in canonical and not any(isinstance(item, dict) and item.get("@type") == "TechArticle" for item in graph): + errors.append(f"{path}: release-notes page lacks TechArticle entity") if not any(isinstance(item, dict) and "speakable" in item for item in graph): errors.append(f"{path}: JSON-LD lacks speakable definition") primary_images = [ @@ -276,6 +323,10 @@ def validate_robots_and_sitemap() -> list[str]: pattern = f"User-agent: {crawler}\nAllow: /" if pattern not in robots: errors.append(f"robots.txt does not explicitly allow {crawler}") + if "Disallow: /*?" not in robots: + errors.append("robots.txt does not exclude parameter URLs from crawl budget") + if "Disallow: /*.json$" not in robots: + errors.append("robots.txt does not exclude raw JSON endpoints from crawl budget") if "Sitemap: https://readmd.asia/sitemap.xml" not in robots: errors.append("robots.txt omits canonical sitemap") sitemap = (PUBLIC / "sitemap.xml").read_text(encoding="utf-8") @@ -449,7 +500,7 @@ def validate_special_page_internal_links() -> list[str]: target_path = urlparse(target).path if f'href="{target_path}"' not in content: errors.append(f"{language} {source} page omits its sibling internal link") - if '"@type":"BreadcrumbList"' not in content: + if not re.search(r'"@type"\s*:\s*"BreadcrumbList"', content): errors.append(f"{language} {source} page omits BreadcrumbList structured data") return errors @@ -528,6 +579,71 @@ def validate_indexnow() -> list[str]: return errors +def validate_feed() -> list[str]: + errors: list[str] = [] + path = PUBLIC / "feed.xml" + if not path.is_file(): + return ["full-site Atom feed is missing"] + try: + root = ET.parse(path).getroot() + except (ET.ParseError, OSError) as exc: + return [f"full-site Atom feed is invalid: {exc}"] + ns = {"atom": "http://www.w3.org/2005/Atom"} + feed_links = [child for child in root if child.tag.endswith("link")] + self_links = [child.get("href") for child in feed_links if child.get("rel") == "self"] + alternate_links = [child.get("href") for child in feed_links if child.get("rel") == "alternate" and child.get("href") != "https://readmd.asia/feed.xml"] + entries = root.findall("atom:entry", ns) + ids = {item.findtext("atom:id", default="", namespaces=ns) for item in entries} + sitemap = (PUBLIC / "sitemap.xml").read_text(encoding="utf-8") + canonical_ids = set(re.findall(r"(.*?)", sitemap)) + if len(entries) != 44 or ids != canonical_ids: + errors.append(f"Atom feed must contain exactly 44 canonical entries, found {len(entries)}") + if self_links != ["https://readmd.asia/feed.xml"]: + errors.append("Atom feed lacks its canonical self link") + if alternate_links != ["https://readmd.asia/"]: + errors.append("Atom feed lacks the homepage alternate link") + if not root.findtext("atom:updated", default="", namespaces=ns): + errors.append("Atom feed lacks an updated timestamp") + for entry in entries: + entry_id = entry.findtext("atom:id", default="", namespaces=ns) + if not entry.findtext("atom:title", default="", namespaces=ns): + errors.append(f"Atom entry lacks title: {entry_id}") + if not entry.findtext("atom:summary", default="", namespaces=ns): + errors.append(f"Atom entry lacks summary: {entry_id}") + return errors + + +def validate_security_txt() -> list[str]: + path = PUBLIC / ".well-known" / "security.txt" + if not path.is_file(): + return ["security.txt is missing"] + text = path.read_text(encoding="utf-8") + required = ( + "Contact: https://github.com/Natsummerance/readMD/security/advisories/new", + "Expires: 2027-08-26T00:00:00Z", + "Preferred-Languages: en, zh-CN, zh-TW, ja", + "Canonical: https://readmd.asia/.well-known/security.txt", + ) + return [] if all(item in text for item in required) else ["security.txt omits required trust fields"] + + +def validate_404() -> list[str]: + path = PUBLIC / "404.html" + if not path.is_file(): + return ["localized-entry 404 page is missing"] + content = path.read_text(encoding="utf-8") + required = ( + '', + 'href="/download/"', + 'href="/workflows/"', + 'href="/release-notes/"', + 'href="/zh-cn/"', + 'href="/zh-tw/"', + 'href="/ja/"', + ) + return [] if all(item in content for item in required) else ["404 page omits recovery entry points"] + + def main() -> int: parser = argparse.ArgumentParser(description="Validate the staged ReadMD website.") parser.add_argument("--release", action="store_true", help="also require a completed dist build") @@ -568,6 +684,9 @@ def main() -> int: errors.extend(validate_answer_internal_links()) errors.extend(validate_indexnow()) errors.extend(validate_release_asset_links()) + errors.extend(validate_feed()) + errors.extend(validate_security_txt()) + errors.extend(validate_404()) if args.release: errors.extend(validate_release_build()) if errors: @@ -580,6 +699,13 @@ def main() -> int: "download_pages": list(DOWNLOAD_PAGES), "answer_pages": list(ANSWER_PAGES), "review_rounds": 3, + "broad_seo": { + "canonical_pages": len(LANGUAGES) + len(INTENT_PAGES) + len(DOWNLOAD_PAGES) + len(ANSWER_PAGES), + "atom_feed": True, + "entity_graph": True, + "security_txt": True, + "quality_404": True, + }, }, ensure_ascii=False)) return 0 diff --git a/website/public/.well-known/security.txt b/website/public/.well-known/security.txt new file mode 100644 index 00000000..0adee955 --- /dev/null +++ b/website/public/.well-known/security.txt @@ -0,0 +1,4 @@ +Contact: https://github.com/Natsummerance/readMD/security/advisories/new +Expires: 2027-08-26T00:00:00Z +Preferred-Languages: en, zh-CN, zh-TW, ja +Canonical: https://readmd.asia/.well-known/security.txt diff --git a/website/public/404.html b/website/public/404.html new file mode 100644 index 00000000..ab5bbfa0 --- /dev/null +++ b/website/public/404.html @@ -0,0 +1,34 @@ + + + + + + Page not found | ReadMD + + + + + + +
+

HTTP 404

+

This page is not part of ReadMD.

+

The address may be outdated or incomplete. Use one of the verified entry points below.

+ +
+

Choose your language

+ +
+
+ + diff --git a/website/public/_headers b/website/public/_headers index 8f5f0caa..f7d0d06b 100644 --- a/website/public/_headers +++ b/website/public/_headers @@ -1,5 +1,5 @@ /* - Content-Security-Policy: default-src 'self'; img-src 'self'; style-src 'self'; script-src 'self' 'sha256-0aSGfJRzOLWRHb9fCc9QS3LYOtwJp2QizvR62pboSS8='; object-src 'none'; base-uri 'self'; frame-ancestors 'none' + Content-Security-Policy: default-src 'self'; img-src 'self'; style-src 'self'; script-src 'self' 'sha256-hTp2CVO7PiwI8t0aW3D3Aj+GKk3WtIgxD6NeYA0qV3A='; object-src 'none'; base-uri 'self'; frame-ancestors 'none' Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff X-Frame-Options: DENY diff --git a/website/public/bibtex-citations/index.html b/website/public/bibtex-citations/index.html index aafbedb5..590e1ff4 100644 --- a/website/public/bibtex-citations/index.html +++ b/website/public/bibtex-citations/index.html @@ -5,7 +5,8 @@ Manage BibTeX Citations | ReadMD Academic Writing - + + @@ -17,6 +18,9 @@ + + + @@ -32,52 +36,157 @@ + + - - + +