Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b4bf564
fix: close web governance review gaps
ShanireZ Aug 26, 2026
a7da435
fix: stabilize web governance evidence
ShanireZ Aug 26, 2026
0aa6a9d
fix: tighten source-backed evidence
ShanireZ Aug 26, 2026
1f0971e
fix: make route evidence semantically precise
ShanireZ Aug 26, 2026
9d41da1
fix: close semantic evidence blind spots
ShanireZ Aug 26, 2026
079f801
fix: isolate semantic route evidence
ShanireZ Aug 26, 2026
defcb64
fix: make semantic write closure fail closed
ShanireZ Aug 26, 2026
921f10c
fix: close semantic mutation evidence gaps
ShanireZ Aug 26, 2026
ee610af
fix: make semantic mutation closure conservative
ShanireZ Aug 26, 2026
435d6f5
fix: close semantic evidence review gaps
ShanireZ Aug 26, 2026
2f41534
fix: close transitive semantic evidence gaps
ShanireZ Aug 26, 2026
6c98fcc
fix: harden semantic evidence boundaries
ShanireZ Aug 26, 2026
f4e474d
fix: close remaining semantic evidence gaps
ShanireZ Aug 26, 2026
e0b015b
fix: preserve semantic callback acquisition
ShanireZ Aug 26, 2026
c7d87a8
fix: close semantic evidence review gaps
ShanireZ Aug 26, 2026
6c7bb55
fix: fail closed on unresolved semantic provenance
ShanireZ Aug 26, 2026
8bca2b1
fix(web): close content governance review gaps
ShanireZ Aug 27, 2026
c72f4b3
fix(web): harden route evidence and negotiation
ShanireZ Aug 27, 2026
01c5cf0
fix(web): close semantic review gaps
ShanireZ Aug 27, 2026
ab1b241
fix(web): close exact-sha evidence gaps
ShanireZ Aug 27, 2026
9ad2713
wip(web): capture remaining evidence review gaps
ShanireZ Aug 27, 2026
6934759
fix(web): 删除过时的视觉资产和交接文档
ShanireZ Aug 27, 2026
4cb7113
fix(web): close destructured and aliased mutator provenance gaps
ShanireZ Aug 27, 2026
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
2 changes: 1 addition & 1 deletion deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ wrangler tail dpmaster

## Sitemap 与搜索平台提交

构建把 sitemap 写进发布目录,线上地址固定为 `https://dp.round1.cc/sitemap.xml`,列出 47 个页面,每条带 Git 派生的 `lastmod`。单域站点不输出 hreflang 互指,`xmlns:xhtml` 命名空间也不再出现。
构建把 sitemap 写进发布目录,线上地址固定为 `https://dp.round1.cc/sitemap.xml`,列出 47 个页面,每条带源码证据派生的 `lastmod`:已提交源码取 Git 提交时间,工作树编辑取文件修改时间,绝不使用模板或构建日期。单域站点不输出 hreflang 互指,`xmlns:xhtml` 命名空间也不再出现。

★ 域名迁移后必须重新提交,旧域名的验证与 sitemap 记录不会自动继承:

Expand Down
9 changes: 5 additions & 4 deletions docs/engineering/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ The manifest intentionally keeps only dependencies imported by current source. D
| `site/src/components/dp-engine/` | Shared visualization, playback, controls, and safe-caption rendering. |
| `site/src/components/games/` | One game per family; games consume public result Interfaces instead of teaching frames. |
| `site/src/components/games/runtime/` | Shared deterministic random source, round statistics, lazy audio, and viewport gate for the seven games. |
| `site/src/config/site.ts` | DP大师 brand, copyright holders, and the single production host with its same-origin API endpoints. |
| `site/src/config/site.ts` | Public representation identity only: DP大师 brand, canonical origin, and language. Client endpoints and sidebar-only copy live in separate modules that the semantic route graph cannot reach. |
| `site/src/lib/publicRoutes.ts` | Catalog-derived authority for 47 public routes plus one separately declared internal prerender specimen. |
| `site/src/lib/publicWebContract.ts` | Shared internal representation paths and public `Content-Signal` contract consumed by generation, discovery, and the Worker. |
| `site/src/lib/pageMeta.ts` | Pure route metadata authority for titles, summaries, canonicals, breadcrumbs, and indexing policy. |
| `site/src/lib/seoHead.ts` | Server/client shared head and JSON-LD generation. |
| `site/src/lib/discovery.ts` | Sitemap, robots (including the AI-crawler group), llms.txt, and route-summary generation. |
Expand All @@ -79,7 +80,7 @@ The manifest intentionally keeps only dependencies imported by current source. D
| `site/src/entry-server.tsx` | React 19 static rendering entry used by the prerender build. |
| `site/scripts/build.mjs` | Builds the client plus an isolated production SSR bundle, then prerenders. |
| `site/scripts/prerender.mjs` | Writes route HTML, one Markdown representation per public route, 404.html, and discovery files. |
| `site/scripts/markdown-representation.mjs` | DOM-based projection from locally prerendered semantic HTML to compact Markdown; it is not a second hand-written content source. |
| `site/scripts/markdown-representation.mjs` | Deterministic DOM-based projection from locally prerendered semantic HTML to compact Markdown with a normalized legal heading outline; it is not a second hand-written content source. |
| `site/worker/` | Shared feedback, privacy-bounded first-party analytics, and egress-probe cores. |
| `site/worker.js` + `site/worker/` | The single deployment adapter: public-page content negotiation plus same-origin feedback, analytics, and egress diagnostics. |

Expand All @@ -99,15 +100,15 @@ All 29 teaching solver surfaces are Adapters over the algorithm boundary: public

Known deep links are physical prerendered HTML assets. Cloudflare Workers Static Assets serves them and uses `404-page` for anything unmatched, returning the themed `404.html` with a real HTTP 404. See root [deploy.md](../../deploy.md) for the platform contract.

For a registered public `GET` or `HEAD`, `worker/content-negotiation.js` selects HTML or Markdown from `Accept`; HTML wins equal preferences and the Worker returns 406 when both supported media types are explicitly unacceptable. Markdown is fetched only through a distinct internal asset URL, so its ETag and static-asset cache key cannot collide with HTML. Both representations add `Vary: Accept`, alternate links, `nosniff`, and the approved public `Content-Signal`; direct requests to the internal prefix return 404. APIs, ordinary assets, the internal specimen, and unknown routes bypass negotiation.
For a registered public `GET` or `HEAD`, `worker/content-negotiation.js` selects HTML or Markdown from `Accept`; HTML wins equal preferences and the Worker returns 406 when both supported media types are explicitly unacceptable. Markdown is fetched only through a distinct internal asset URL, so its ETag and static-asset cache key cannot collide with HTML. Both representations add `Vary: Accept`, alternate links, `nosniff`, and the approved public `Content-Signal`; direct requests to the internal prefix return 404. `HEAD` executes the same matrix and headers but never returns a body. APIs, ordinary assets, the internal specimen, and unknown routes bypass negotiation. The internal path mapper and public signal live in `publicWebContract.ts`, preventing the generator, discovery files, and Worker from silently diverging.

# SEO And Accessibility

`pageMeta.ts` is the shared route metadata authority. The site has a single origin, so every public page canonicalizes to itself and publishes no language alternates at all. Unknown routes are non-indexable and deliberately have no canonical link.

`seoHead.ts` renders the same head contract at build time that `RouteMeta` maintains after client navigation: title, description, citable abstract, robots, canonical, Open Graph, Twitter, and a Schema.org graph. Every indexable page includes Organization and WebSite nodes and states `isAccessibleForFree`; the WebSite node declares a `SearchAction` pointing at the real `?q=` problem search. Lessons add Course/LearningResource/TechArticle with `educationalUse` and an `EducationalAudience`, family pages add CollectionPage plus an ItemList of their ready lessons, other pages add WebPage, and routes with hierarchy add BreadcrumbList. Course duration is deliberately not declared: there is no trustworthy source for it, so `hasCourseInstance` is omitted rather than fabricated.

`site/scripts/generate-seo.mjs` writes the checked-in baseline for `sitemap.xml`, `robots.txt`, `llms.txt`, `route-summaries.json`, and the home-route head inside `index.html`; the prerender step regenerates the four discovery files into the build output. All of them derive from the same 47-path catalog contract: home, seven families, 37 completed lessons, method, and problem index. `llms.txt` is grouped by family and declares the same-URL Markdown interface and approved public content signal. `robots.txt` explicitly allows both retrieval and training crawlers; a release is incomplete until Cloudflare AI Crawl Control is confirmed not to inject a conflicting managed no-train block. `pnpm check:seo` rejects generated-artifact drift, including a hand-edited `index.html` head.
`site/scripts/generate-seo.mjs` writes the checked-in baseline for `sitemap.xml`, `robots.txt`, `llms.txt`, `route-summaries.json`, and the home-route head inside `index.html`; the prerender step regenerates the four discovery files into the build output. All of them derive from the same 47-path catalog contract: home, seven families, 37 completed lessons, method, and problem index. Sitemap entries are sorted by canonical URL. `semantic-source-graph.mjs` follows the real SSR injection chain from `entry-server.tsx` through the static application/providers and route tree, together with metadata roots and the exact route page, lesson, and family-art modules. Mixed shell modules use an AST-rooted projection: the route-rendering JSX plus the closure of local declarations and render-phase assignments reached by lexical binding identity are hashed. Control predicates, loop/switch scopes, shadowed names, class helpers, route/outlet bindings, and class-field/member identity remain precise; effect-only callbacks and JSX attribute names do not leak in. A nested write outside the render or declaration execution scope is accepted only for a recognized client-effect callback; any other ambiguous nested write fails generation and must be refactored instead of silently producing incomplete evidence. The graph deliberately prunes surrounding navigation/feedback, unrelated route pages, client-only analytics/theme/motion controllers, and the generated evidence module. Public representation identity is structurally isolated in `config/site.ts`; client endpoints and sidebar-only copy are in modules reachable only through pruned branches. Each route records a versioned, line-ending-neutral SHA-256 digest of that graph. Clean sources use the latest Git commit timestamp, dirty sources use the newest source-file modification timestamp, an unchanged digest preserves the previous value, and generation fails when changed content lacks strictly advancing source evidence; changing the digest schema preserves the prior content timestamp only when no newer source evidence exists, while a simultaneous real content update adopts its later timestamp. Version 4 was reseeded from the trusted `2026-08-22` history: the home route uses the visible hero change at `23:46:11+08:00`, and the other 46 routes use the single-origin metadata/content change at `11:47:40+08:00`. Earlier rejected schemas had admitted a shared client effect, sidebar-only copyright copy, or incomplete execution/scope context. `llms.txt` is grouped by family and declares the same-URL Markdown interface and approved public content signal. `robots.txt` explicitly allows both retrieval and training crawlers; a release is incomplete until Cloudflare AI Crawl Control is confirmed not to inject a conflicting managed no-train block. `pnpm check:seo` rejects generated-artifact drift, including a hand-edited `index.html` head.

# Analytics

Expand Down
9 changes: 6 additions & 3 deletions docs/operations/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Pure TypeScript Modules imported by Node tests must use explicit `.ts` extension

# SEO And Accessibility Checks

`pnpm check:seo` verifies the 47-path catalog, route metadata, checked-in single-origin sitemap/robots baseline, `llms.txt`, 47-entry route summaries, and Git-derived `lastmod` data. It also checks the source contracts for the one Cloudflare output, React prerender/hydration, route CSS injection, canonical metadata, and real 404 behavior. `pnpm check:html` then requires the sitemap/public-route/Markdown sets to agree, rejects missing or extra Markdown assets, browser-only markup and internal-path leakage, and requires each Markdown representation to remain less than half the size of its corresponding HTML. When routes, lesson readiness, or route-owned source files change, run `pnpm seo:generate` and review the generated files before committing them.
`pnpm check:seo` verifies the 47-path catalog, route metadata, checked-in single-origin sitemap/robots baseline, `llms.txt`, 47-entry route summaries, and source-backed `lastmod` data. A line-ending-neutral SHA-256 digest follows each route's real SSR injection chain and exact semantic content graph while pruning navigation, feedback, analytics, theme, and motion-only branches. Mixed shell modules contribute an AST-rooted closure of route-rendering JSX, local declarations, and render-phase assignments resolved by lexical binding identity. Regression probes cover control predicates, function/loop/switch shadowing, class fields, later assignments, route/outlet bindings, effect-only writes, and non-reference JSX names; ambiguous nested writes outside known client-effect callbacks fail closed. Client endpoints and sidebar-only copy are structurally isolated from the public representation configuration and are unreachable from all 47 semantic graphs. Clean sources use their latest Git commit timestamp; dirty sources use their newest filesystem modification timestamp; and generation fails if the digest changes without that evidence advancing. A digest-schema migration preserves the prior timestamp only when it has no newer source evidence; a simultaneous content edit must still advance. Version 4 was explicitly reseeded from trusted `2026-08-22` Git history: `23:46:11+08:00` for the home hero change and `11:47:40+08:00` for the other 46 routes' latest single-origin metadata/content change. Earlier rejected schemas admitted a shared client effect, sidebar-only copyright copy, or incomplete execution/scope context. This keeps same-day edits precise while unchanged or cross-platform regeneration remains deterministic. The gate also checks the source contracts for the one Cloudflare output, React prerender/hydration, route CSS injection, canonical metadata, and real 404 behavior. `pnpm check:html` then requires the sitemap/public-route/Markdown sets to agree; rejects missing or extra Markdown assets, browser-only markup, internal-path leakage, illegal heading hierarchies, and unresolved same-origin links; regenerates every Markdown document twice from its matching semantic HTML to prove exact same-source parity and determinism; and requires each representation to remain less than half the size of its HTML. When routes, lesson readiness, or route-owned source files change, run `pnpm seo:generate` and review the generated files before committing them.

The browser gate runs Chromium against built `dist/` through a strict, non-reused custom preview server. Route tests directly open `/`, `/part/a`, `/part/a/01`, `/method`, and `/part/g/plug`, then exercise live client navigation and keyboard focus. They check HTTP status, prerendered HTML, absence of pending streamed-Suspense placeholders, route CSS at first paint, hydration without errors, CLS below `0.05`, title/description/abstract/canonical/zero-hreflang/Open Graph/JSON-LD metadata, one visible `h1`, route announcements, current-page semantics, initial-load focus, changed-route focus, and skip-link focus. Navigation-positioning tests click a representative lesson outline in each of the seven families, restore a direct lesson fragment, verify the mobile outline and an ordinary family-page fragment, and require a changed route to reset old-page scroll immediately. An unknown path must return 404 with `noindex,nofollow`, no canonical, and the themed not-found heading. Worker-level Node tests separately cover the Accept matrix, 406 behavior, HEAD parity, representation-specific ETags, hidden internal assets, and unaffected API/static/unknown paths.

Expand Down Expand Up @@ -95,8 +95,11 @@ When changing lesson content:

Deployment checks live in root [deploy.md](../../deploy.md). At minimum after deployment:

* Open a normal route and a direct deep link on both domains.
* Confirm each domain self-canonicalizes and links to both regional variants.
* Open a normal route and a direct deep link on `https://dp.round1.cc`; confirm both self-canonicalize and emit zero hreflang links.
* Run GET and HEAD against a public route with `Accept: text/html` and `Accept: text/markdown`, then request an unacceptable media type and confirm 406.
* Confirm both successful representations carry distinct ETag values, `Vary: Accept`, the appropriate alternate/canonical `Link`, and `Content-Signal: ai-train=yes, search=yes, ai-input=yes`.
* Alternate HTML, Markdown, and HTML requests through the final edge to prove the cache never crosses representations; direct requests under `/_representations/` must return 404.
* Inspect the final `robots.txt` and confirm Cloudflare has not injected `ai-train=no` or crawler `Disallow` rules that conflict with the repository policy.
* Request a made-up path and confirm HTTP 404 plus `noindex,nofollow`.
* Submit a feedback test.
* Confirm `POST /api/analytics` returns 204 for a valid same-origin event and inspect the platform log.
Expand Down
2 changes: 1 addition & 1 deletion site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<meta name="twitter:title" content="DP大师 · DP Master" />
<meta name="twitter:description" content="DP大师面向算法学习者,用精讲、逐帧可视化、题目索引和小游戏讲清状态定义、转移顺序与模型迁移。" />
<meta name="twitter:image" content="https://dp.round1.cc/og/dpmaster-social.jpg" />
<script id="dp-structured-data" type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://dp.round1.cc/#publisher","name":"AzureL蔚澜算法","url":"https://dp.round1.cc","logo":"https://dp.round1.cc/favicon.svg","brand":{"@type":"Brand","name":"DP大师"}},{"@type":"WebSite","@id":"https://dp.round1.cc/#website","name":"DP大师","alternateName":"DP Master","url":"https://dp.round1.cc/","description":"DP大师用精讲、逐帧可视化、题目索引和小游戏讲清动态规划。","inLanguage":"zh-Hans","publisher":{"@id":"https://dp.round1.cc/#publisher"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://dp.round1.cc/problems?q={search_term_string}"},"query-input":"required name=search_term_string"}},{"@type":"WebPage","@id":"https://dp.round1.cc/#webpage","url":"https://dp.round1.cc/","name":"DP大师 · DP Master","description":"DP大师面向算法学习者,用精讲、逐帧可视化、题目索引和小游戏讲清状态定义、转移顺序与模型迁移。","abstract":"DP大师面向算法学习者,用精讲、逐帧可视化、题目索引和小游戏讲清状态定义、转移顺序与模型迁移。","inLanguage":"zh-Hans","isPartOf":{"@id":"https://dp.round1.cc/#website"},"publisher":{"@id":"https://dp.round1.cc/#publisher"},"isAccessibleForFree":true,"dateModified":"2026-08-22","image":"https://dp.round1.cc/og/dpmaster-social.jpg"}]}</script>
<script id="dp-structured-data" type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://dp.round1.cc/#publisher","name":"AzureL蔚澜算法","url":"https://dp.round1.cc","logo":"https://dp.round1.cc/favicon.svg","brand":{"@type":"Brand","name":"DP大师"}},{"@type":"WebSite","@id":"https://dp.round1.cc/#website","name":"DP大师","alternateName":"DP Master","url":"https://dp.round1.cc/","description":"DP大师用精讲、逐帧可视化、题目索引和小游戏讲清动态规划。","inLanguage":"zh-Hans","publisher":{"@id":"https://dp.round1.cc/#publisher"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://dp.round1.cc/problems?q={search_term_string}"},"query-input":"required name=search_term_string"}},{"@type":"WebPage","@id":"https://dp.round1.cc/#webpage","url":"https://dp.round1.cc/","name":"DP大师 · DP Master","description":"DP大师面向算法学习者,用精讲、逐帧可视化、题目索引和小游戏讲清状态定义、转移顺序与模型迁移。","abstract":"DP大师面向算法学习者,用精讲、逐帧可视化、题目索引和小游戏讲清状态定义、转移顺序与模型迁移。","inLanguage":"zh-Hans","isPartOf":{"@id":"https://dp.round1.cc/#website"},"publisher":{"@id":"https://dp.round1.cc/#publisher"},"isAccessibleForFree":true,"dateModified":"2026-08-22T23:46:11+08:00","image":"https://dp.round1.cc/og/dpmaster-social.jpg"}]}</script>
<!-- dp-route-head:end -->
</head>
<body>
Expand Down
Loading