Skip to content

feat(site): refresh landing demos, stats, and metadata#432

Merged
hiqiancheng merged 17 commits into
TouchAI-org:mainfrom
TheEverests:codex/site-scroll-demo-fixes
Jun 17, 2026
Merged

feat(site): refresh landing demos, stats, and metadata#432
hiqiancheng merged 17 commits into
TouchAI-org:mainfrom
TheEverests:codex/site-scroll-demo-fixes

Conversation

@TheEverests

@TheEverests TheEverests commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

First external contributors may need to complete the CLA Assistant check before merge.
For code changes, this PR must link the related issue or RFC in the section below.

Summary

  • Refresh the TouchAI landing page demos and supporting site assets.
  • Stabilize the embedded scroll-driven demo layout across desktop and mobile viewports.
  • Replace repeated demo markdown/math helpers with lightweight shared demo utilities.
  • Fix GitHub repo stats so Open issues counts only open issues, not pull requests.
  • Add supporting site metadata and public assets: SEO/Open Graph metadata, JSON-LD, PWA manifest/icons, robots.txt, sitemap reference, Starlight docs collection wiring, a custom 404 page, and a getting-started page.
  • Add optional first-party analytics endpoint support. It is disabled unless PUBLIC_ANALYTICS_ENDPOINT is set; when enabled it sends pageview/click metadata such as URL, path, title, language, referrer, viewport, event type, and link label.
  • Keep the English solver demo English-first by translating its visible fallback answer and accessibility text.

Scope note

This PR is intentionally apps/site-only. It does not touch desktop app runtime, AgentService, MCP, database schema, release packaging, or Rust behavior. The scope is broader than a narrow demo bugfix: it updates the landing experience, repo stats, site metadata, docs surface, and optional analytics plumbing together as a follow-up to the prior site refresh work.

The English solver demo now uses English page chrome, fallback content, and accessibility strings. The problem image is still described as a screenshot of a Chinese conic-section math problem, because the source problem image itself is Chinese.

Related issue or RFC

This is a follow-up to the previously merged site refresh and responsive/demo polish PRs.

AI assistance disclosure

  • Tool(s) used: OpenAI Codex
  • Scope of assistance: site implementation cleanup, Git branch consolidation, local build/browser verification, validation follow-up, English demo accessibility polish, and PR description preparation
  • Human review or rewrite performed: I reviewed the affected site changes, checked the rendered page in-browser, and verified the final PR branch before submission.
  • Architecture or boundary impact: none outside apps/site; no desktop/runtime/database boundaries are changed.

Testing evidence

pnpm --filter @touchai/site build
# passed

Additional local browser verification:

http://localhost:4321/#teams
# GitHub stats displayed as: 42 / 24 / 72
# Console errors observed: 0

Additional review validation on the PR head:

node %TEMP%\check-solver-en.js
# passed: blocked Chinese UI/ARIA strings are absent from feature-solver-en

rg -n "[\p{Han}]" apps/site/public/feature-solver-en/touchai-components.html
# no matches: English solver demo file has no remaining Han characters

pnpm exec prettier --check --ignore-unknown <changed apps/site text files>
# passed

pnpm exec eslint apps/site/src/content.config.ts \
  apps/site/public/demo-utils/touchai-lite-math.js \
  apps/site/public/demo-utils/touchai-lite-renderer.js \
  apps/site/src/scripts/component-demo-runtime.ts
# passed

pnpm test:pr was not completed successfully locally. Earlier local runs were blocked by the RTK asset download path; the latest review run got farther but exhausted local disk space during Rust/C compilation (no space on device in check:rust). Because this PR only changes apps/site, full desktop PR verification should be covered by CI or rerun on a machine with enough Rust build cache space before merge.

Did you follow TDD? Partially. A small content assertion was run red/green for the English solver demo accessibility strings, then the site files were verified with Prettier, ESLint, and the site build. No broader automated site test suite exists in apps/site.

Risk notes

  • AgentService, runtime, MCP, or schema impact: none
  • database baseline or migration impact: none
  • release or packaging impact: site-only static assets and landing page behavior
  • privacy/analytics impact: optional analytics is disabled by default; deployments that set PUBLIC_ANALYTICS_ENDPOINT should provide appropriate disclosure/consent for collected pageview and click metadata

Screenshots or recordings

Local browser verification was performed at:

http://localhost:4321/#teams

Observed stats:

42 Stars / 24 Forks / 72 Open issues

Checklist

  • The PR title follows Conventional Commits and is valid for squash merge.
  • This PR is either ready for review or explicitly marked as a Draft PR.
  • I did not use [WIP] or similar title prefixes.
  • If AI materially assisted this PR, I disclosed the tools and scope and I personally reviewed every affected change.
  • I can explain the why, what, and how of this change without relying on an AI tool.
  • If this touches AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC. N/A.
  • If this changes architecture or adds a new cross-boundary abstraction, there is an accepted RFC. N/A.
  • I ran pnpm test:pr for this code PR, or this is a docs-only change. Local run did not complete; see validation notes above.
  • If I changed Rust behavior or tests, I reviewed pnpm test:coverage:rust or relied on CI coverage evidence. N/A.
  • If I changed desktop startup/window/search/popup/settings/E2E paths, I ran pnpm test:e2e locally or documented why CI is the first valid proof. N/A.
  • I added tests or explained why tests are not appropriate.
  • I updated docs when behavior changed.

@github-actions github-actions Bot added the area:frontend Frontend UI or view-layer changes label Jun 6, 2026
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two shared lightweight JavaScript rendering utilities (TouchAILiteRenderer, TouchAILiteMathRenderer) and migrates all demo components from markdown-it to these utilities. Refactors scroll-driven state, accessibility (aria-busy/aria-live), and postMessage handling uniformly across six demo HTML files. Updates the demo host runtime for message queuing and ResizeObserver tracking. Overhauls the homepage with richer SEO metadata, analytics tracking, dataset-driven GitHub stats, and queued scroll-progress dispatch. Adds site manifest, robots.txt, Starlight docs content, and env variable updates.

Changes

Demo rendering, runtime, and component flow

Layer / File(s) Summary
Lightweight renderer and math utilities
apps/site/public/demo-utils/touchai-lite-renderer.js, apps/site/public/demo-utils/touchai-lite-math.js
window.TouchAILiteRenderer exposes escapeHtml, renderInline, and renderMarkdownContent; window.TouchAILiteMathRenderer exposes renderFormula (KaTeX-first with fallback parser) and renderMarkdownWithMath.
Demo host runtime and container CSS
apps/site/src/scripts/component-demo-runtime.ts, apps/site/src/components/ComponentDemo.astro
TouchAiHost switches from single pending message to an array queue and tracks ResizeObserver instances; dispatchMessage classifies scroll-driven types and queues/flushes; host CSS updated for scroll-driven will-change, scrollable conversation-content, and margin-top: auto composer placement; touchai-component-demo gains role="region" and aria-label.
Intro components scroll-driven and messaging refactor
apps/site/public/touchai-intro/touchai-components.html, apps/site/public/touchai-intro-en/touchai-components.html
Switches to touchai-lite-renderer.js; adds resetDemoState/setScrollDrivenState/keepLatestResponseVisible/scheduleScrollAffordanceUpdate/forwardWheelToPage helpers; tightens scroll-progress thresholds; toggles aria-busy through typewriter lifecycle; rewires postMessage to use getMessagePayload/notifyParent; English ARIA labels in -en variant.
Reminder components scroll-driven and rendering refactor
apps/site/public/feature-reminder/touchai-components.html, apps/site/public/feature-reminder-en/touchai-components.html
Switches to touchai-lite-renderer.js; adds setToolCallVisibility helper; refactors scroll-driven CSS (overscroll containment, composer pinning, h2 visibility tracking); centralizes demo reset/state helpers; rewires parent messaging with new message types; English ARIA labels in -en variant.
Solver components rendering, localization, and progress orchestration
apps/site/public/feature-solver/touchai-components.html, apps/site/public/feature-solver-en/touchai-components.html
Switches to touchai-lite-renderer.js + touchai-lite-math.js; -en variant fully localizes solution content, toolbar, and composer ARIA labels to English; adds resetDemoState/setScrollDrivenState/scroll-UX helpers; optimizes syncMathLineGeometry with cached width/offset and RAF; refactors showAnswer, typePromptThenSubmit, and renderScrollProgress.
Work organizer components scroll lifecycle refactor
apps/site/public/feature-work-organizer/touchai-components.html, apps/site/public/feature-work-organizer-en/touchai-components.html
Switches to touchai-lite-renderer.js; adds scroll-driven CSS overrides and h2 visibility extension; introduces getMessagePayload/notifyParent; rewrites renderScrollProgress with threshold gating; adds keepLatestResponseVisible/scheduleScrollAffordanceUpdate/forwardWheelToPage; handles new message types; English ARIA labels in -en variant.

Homepage SEO, analytics, stats, and demo progress sync

Layer / File(s) Summary
Head metadata, responsive layout, and content updates
apps/site/src/pages/index.astro
Adds centralized SEO/repo constants, expanded OpenGraph/Twitter/hreflang/JSON-LD head, .sr-only utility, feature-card variable-driven scaling with top-left transforms, responsive breakpoint retunes, dataset-driven GitHub stats markup, analytics event attributes on CTAs/footer, and translation string updates.
Analytics tracking, GitHub stats, and env wiring
apps/site/.env.example, apps/site/src/pages/index.astro
Replaces PUBLIC_WAITLIST_ENDPOINT with PUBLIC_ANALYTICS_ENDPOINT; client runtime tracks one-time pageview and [data-analytics-event] clicks via sendBeacon; GitHub stats overhaul reads endpoint/initial values from dataset, normalizes with Intl.NumberFormat, persists to localStorage, and refreshes via interval/visibility/focus/online triggers.
Queued demo progress orchestration and ScrollTrigger sync
apps/site/src/pages/index.astro
Implements queued/snapped scroll-progress dispatch with deduping, cancellation, and "clear scroll-driven" idle signaling; updates solver/organizer/reminder "ready" handling with getMessageType; refactors ScrollTrigger leave/leaveBack to enqueue progress and send touchai-clear-scroll-driven on cleanup; adds compact/short-desktop viewport helpers.

Site config, public metadata files, and docs content

Layer / File(s) Summary
Site configuration and public metadata assets
apps/site/astro.config.mjs, apps/site/src/content.config.ts, apps/site/public/robots.txt, apps/site/public/site.webmanifest
Adds disable404Route: true to Starlight, defines docs content collection via docsLoader/docsSchema, adds robots.txt with sitemap reference, and adds PWA manifest with standalone display and icon set.
Documentation pages for onboarding and 404 route
apps/site/src/content/docs/404.md, apps/site/src/content/docs/getting-started.md
Adds splash-template 404 page linking to /getting-started/, and adds Chinese quick-start documentation with install steps, usage flow, and GitHub Issues/Releases links.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (scroll)
  participant Page as index.astro (ScrollTrigger)
  participant Runtime as component-demo-runtime.ts
  participant Demo as touchai-components.html (iframe)

  User->>Page: scroll through feature section
  Page->>Runtime: dispatchMessage({type: "touchai-set-scroll-progress", progress: 0.4})
  alt handlers registered
    Runtime->>Demo: deliver progress message
    Demo->>Demo: renderScrollProgress(0.4)
    Demo->>Demo: setScrollDrivenState(true)
    Demo->>Demo: keepLatestResponseVisible()
  else handlers not yet registered
    Runtime->>Runtime: queue message (replace by type)
  end
  Demo->>Runtime: notifyParent("touchai-solver-ready")
  Runtime->>Runtime: flushPendingMessages(host)
  Runtime->>Demo: deliver queued progress message

  User->>Page: scroll past feature section
  Page->>Runtime: dispatchMessage({type: "touchai-clear-scroll-driven"})
  Runtime->>Demo: clear scroll-driven state
  Demo->>Demo: setScrollDrivenState(false)
  Demo->>Demo: aria-busy="false"
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • TouchAI-org/TouchAI#275: Both PRs modify apps/site/.env.example and site analytics wiring — PR #275 introduced the waitlist endpoint that this PR replaces with PUBLIC_ANALYTICS_ENDPOINT.
  • TouchAI-org/TouchAI#373: Both PRs touch apps/site/src/scripts/component-demo-runtime.ts and ComponentDemo.astro for message/scroll-driven host handling and demo scaffolding.
  • TouchAI-org/TouchAI#374: Both PRs modify embedded demo host/layout CSS in ComponentDemo.astro and component-demo-runtime.ts, overlapping on .component-frame sizing and responsive breakpoints.

Suggested reviewers

  • hiqiancheng

Poem

🐰 A rabbit refactored the scrolling today,
Swapped markdown-it for a lite parser's way.
aria-busy now toggles with care,
KaTeX or fallback — math rendered with flair.
The queue holds all messages snug and tight,
While ScrollTrigger syncs demo frames just right! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@coderabbitai coderabbitai Bot requested a review from hiqiancheng June 6, 2026 19:40

@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.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/site/public/touchai-intro-en/touchai-components.html (1)

1651-1702: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard the autoplay helper after the demo has already started.

typePromptThenSubmit() can still fire from the 900ms fallback after a user-triggered start, which clears the prompt and restarts the sequence mid-animation.

Suggested fix
                 function showAnswer() {
+                    window.clearTimeout(fallbackTimer);
                     hasReceivedScrollProgress = false;
                     setScrollDrivenState(false);
                     clearPromptTyping();
@@
                 function typePromptThenSubmit() {
+                    if (
+                        hasStarted ||
+                        document.body.classList.contains('is-answering') ||
+                        document.body.classList.contains('is-complete')
+                    ) {
+                        return;
+                    }
+
+                    window.clearTimeout(fallbackTimer);
                     hasReceivedScrollProgress = false;
                     setScrollDrivenState(false);
                     clearPromptTyping();

Also applies to: 1757-1767

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/site/public/touchai-intro-en/touchai-components.html` around lines 1651
- 1702, typePromptThenSubmit() can run after the demo has already started and
restart the sequence mid-animation; guard its work by checking the same start
state used elsewhere: at the top of typePromptThenSubmit() and immediately
before enqueuing/performing each timed step (inside typeNextPromptCharacter and
before the submitButton.click callback) verify hasStarted is false (or that
document.body does not contain 'is-answering'); if the demo has started, cancel
timers (promptTypingTimer) and return early. Reference symbols:
typePromptThenSubmit, typeNextPromptCharacter, promptTypingTimer,
submitButton.click, hasStarted, showAnswer, submitPrompt.
apps/site/src/scripts/component-demo-runtime.ts (1)

595-614: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Flush queued messages when the first message handler is attached.

Right now pending messages only drain at the end of applyDocument(). If a demo registers its message listener from DOMContentLoaded, load, or any later async path, queued progress/reset messages stay stranded forever.

Suggested fix
                         windowHandlers.set(
                             handler,
                             wrappedMessageHandler as unknown as EventListenerOrEventListenerObject
                         );
                         messageHandlers.add(wrappedMessageHandler);
                         host.dataset.touchaiMessageHandlerCount = String(messageHandlers.size);
+                        flushPendingMessages(host);
                         return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/site/src/scripts/component-demo-runtime.ts` around lines 595 - 614, When
attaching a new 'message' handler, detect if this is the first handler (i.e.,
messageHandlers.size === 0 before adding) and, if so, immediately flush the
queued/pending messages the same way applyDocument() does so stranded
progress/reset messages are delivered; implement by calling the existing
queue-drain logic (reuse the function or block used in applyDocument() that
processes the pending message queue) right after registering
wrappedMessageHandler and updating host.dataset.touchaiMessageHandlerCount so
queued messages are not left stranded.
apps/site/public/touchai-intro/touchai-components.html (1)

1722-1770: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset hasReceivedScrollProgress when switching back to manual playback.

The English intro file clears this flag in both manual entry paths, but this localized version does not. After a scrub-driven run, replay/manual start stays on the “external progress received” path.

Suggested fix
                 function showAnswer() {
+                    hasReceivedScrollProgress = false;
                     setScrollDrivenState(false);
                     clearPromptTyping();
                     clearPromptInput();
@@
                 function typePromptThenSubmit() {
+                    hasReceivedScrollProgress = false;
                     setScrollDrivenState(false);
                     clearPromptTyping();
                     input.value = '';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/site/public/touchai-intro/touchai-components.html` around lines 1722 -
1770, Reset the hasReceivedScrollProgress flag when switching back to manual
playback: set hasReceivedScrollProgress = false inside showAnswer() (so any
manual showAnswer path clears external-scroll state) and also set
hasReceivedScrollProgress = false at the start of typePromptThenSubmit()
(immediately after setScrollDrivenState(false)) so the typed/manual entry path
likewise clears the flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/site/public/demo-utils/touchai-lite-math.js`:
- Around line 2-6: escapeHtml currently returns raw strings when
window.TouchAILiteRenderer is unavailable, causing unescaped HTML to be injected
by renderMarkdownWithMath; replace the fallback with a proper escaping routine:
keep using renderer.escapeHtml(value) when renderer exists, otherwise implement
a local escape that converts &, <, >, ", ', and ` to their HTML entities; update
the escapeHtml function (and the identical fallback used around lines 229-231)
so renderMarkdownWithMath always receives safely escaped text.

In `@apps/site/public/feature-reminder-en/touchai-components.html`:
- Around line 1476-1482: The resetVisibleBlocks function's selector set is
missing tool card classes so .tool-call-list and .tool-call elements remain
visible after a reset; update the querySelectorAll call inside
resetVisibleBlocks to include '.tool-call-list' and '.tool-call' alongside the
existing selectors so those elements have 'is-visible' removed during resets.

In `@apps/site/public/feature-reminder/touchai-components.html`:
- Around line 1575-1595: The wheel handler forwardWheelToPage currently prevents
default and calls window.scrollBy, which only scrolls the iframe; instead
forward the computed delta (nextDeltaY) to the host so the embedding page can
handle scrolling—use window.parent.postMessage (or the existing postMessage
channel) to send a message containing { type: 'wheel-delta', deltaY: nextDeltaY
} when isScrollDriven && hasReceivedScrollProgress are true, keep the ctrl/meta
early-return logic, and remove or stop relying on window.scrollBy to avoid
trapping wheel input inside the iframe; reference forwardWheelToPage,
isScrollDriven, hasReceivedScrollProgress and the computed nextDeltaY when
implementing.
- Around line 496-500: The rule body.is-scroll-driven.is-complete .chat-panel is
overriding the mobile rule body.is-complete .chat-panel and reintroducing the
fixed 657px height on short viewports; limit this selector to desktop (e.g.,
wrap it in a desktop media query) or add a matching mobile override that
restores the mobile height behavior so short viewports don’t get the fixed
657px: update the CSS where body.is-scroll-driven.is-complete .chat-panel is
defined and either scope it with a desktop-only media query or add a
body.is-scroll-driven.is-complete .chat-panel mobile-specific rule that mirrors
the existing body.is-complete .chat-panel mobile override.

In `@apps/site/public/feature-solver-en/touchai-components.html`:
- Around line 1968-1988: The wheel handler forwardWheelToPage is trapping wheel
input because it calls event.preventDefault() and uses window.scrollBy() (which
only scrolls the iframe); instead forward the intended scroll to the host and
stop preventing the native event. Modify forwardWheelToPage to remove the
event.preventDefault() call that runs before handing the event off and replace
the iframe-only window.scrollBy(0, nextDeltaY) with a parent.postMessage({type:
'forward-wheel', deltaY: nextDeltaY}) (or, if the iframe and host are
same-origin, call parent.scrollBy(0, nextDeltaY)), keeping the existing guards
(isScrollDriven, hasReceivedScrollProgress) intact so only relevant wheel events
are forwarded.
- Line 2: The page declares lang="en" but still contains Chinese copy in
accessibility/fallback text (e.g., the strings near Line 982, Line 1023 and the
prerendered answer block starting at Line 1049), so either translate those
Chinese strings into English or revert lang to "zh" until translation is done;
specifically locate and update the Chinese text in the prerendered answer body,
any no-JS/failed-script fallback content, and ARIA/alt/label attributes (search
for the prerendered answer container and fallback block IDs/classes and the text
nodes around the noted lines) to their English equivalents so screen readers and
fallbacks get correct language context before keeping <html lang="en">.
- Around line 426-430: The selector body.is-scroll-driven.is-complete
.chat-panel is overriding the mobile-specific rule and forcing the fixed 657px
height on small screens; modify the CSS so the scroll-driven completion rule
does not apply the fixed height on narrow viewports—e.g., add a
media-query-specific override for body.is-scroll-driven.is-complete .chat-panel
(or alter that selector) to restore the mobile-safe properties (remove or unset
min-height/max-height/height or set them to auto/none) so the existing
body.is-complete .chat-panel mobile rule remains effective on small screens.

In `@apps/site/public/feature-solver/touchai-components.html`:
- Around line 1968-1988: The current forwardWheelToPage handler prevents default
and calls window.scrollBy, which only scrolls the iframe and swallows the wheel
for the parent; instead, stop preventing default and stop using window.scrollBy
inside the iframe — forward the computed delta to the host page (e.g., via
postMessage) so the parent can perform the scroll or allow the event to bubble
to the parent by removing event.preventDefault() before window.scrollBy; update
forwardWheelToPage to (1) keep the ctrl/meta early preventDefault branch, (2)
compute deltaMultiplier/nextDeltaY as now, and (3) either postMessage({type:
'wheel', deltaY: nextDeltaY}) to parent or simply remove the final
event.preventDefault() + window.scrollBy call so the host receives the wheel
event.
- Around line 426-430: The new selector body.is-scroll-driven.is-complete
.chat-panel is overriding the mobile rule body.is-complete .chat-panel and
forcing a fixed desktop height on short screens; update the CSS to preserve the
mobile override by adding a mobile-specific rule that targets the scroll-driven
completion state (e.g., inside the same small-screen media query add
body.is-scroll-driven.is-complete .chat-panel and set
min-height/max-height/height to the mobile variable or the previous values), or
increase specificity on the existing mobile selector so
body.is-scroll-driven.is-complete .chat-panel does not win.

In `@apps/site/src/content.config.ts`:
- Around line 1-3: The import block is out of order and failing
simple-import-sort; reorder the imports so external package imports are grouped
and sorted before local/alias imports—specifically ensure 'astro:content'
(defineCollection) and '`@astrojs/starlight/`...' imports (docsLoader, docsSchema)
follow the project's import-sort rules (or run the autofixer) so the imports for
defineCollection, docsLoader, and docsSchema are in the correct sorted groups
and order.

In `@apps/site/src/pages/index.astro`:
- Around line 37-52: The fetch blocks for repoApiUrl and repoOpenIssuesApiUrl
currently swallow all errors (empty catch) and ignore non-ok responses; update
the try/catch handlers around fetch(repoApiUrl, { headers }) and
fetch(repoOpenIssuesApiUrl, { headers }) to log diagnostics: on non-ok responses
log the URL and response.status/response.statusText, and in the catch blocks log
the thrown Error (including message/stack) along with which fetch (repoApiUrl or
repoOpenIssuesApiUrl) failed so build-time failures are visible (use your
existing logger or console.error).
- Around line 2685-2738: Add developer-facing privacy documentation and an
in-code comment noting legal/privacy implications when enabling analytics:
update the .env.example to document that PUBLIC_ANALYTICS_ENDPOINT enables
collection of referrer, viewport, and lang which may trigger GDPR/CCPA
disclosure/consent requirements, and add a clear comment immediately above the
sendAnalytics function (and mention analyticsEndpoint and trackPageView)
explaining what fields are collected, that tracking is opt-in via the env var,
and that deployers must ensure appropriate user notices/consent before enabling
it.
- Around line 1814-1817: The ANALYTICS_ENDPOINT environment value is embedded
directly into data-analytics-endpoint (and used by client fetches) without
validating its URL format; update the front-matter where ANALYTICS_ENDPOINT is
read to validate it (e.g., attempt new URL(ANALYTICS_ENDPOINT) or RegExp) and if
invalid set it to null/empty or a safe fallback, then only render the
data-analytics-endpoint attribute when the validated value exists; also update
the client-side code that reads dataset.analyticsEndpoint to check for a truthy,
valid URL before calling fetch to avoid silent failures.
- Around line 2907-2923: fetchRepoOverview currently adds a timestamp query via
withCacheBuster and also sets fetch option cache: 'no-store' — remove the
redundant timestamp approach: stop calling withCacheBuster(REPO_STATS_API_URL)
and withCacheBuster(REPO_OPEN_ISSUES_API_URL) and pass the plain
REPO_STATS_API_URL and REPO_OPEN_ISSUES_API_URL to fetch (leave cache:
'no-store' in the fetch options); update both fetch calls inside
fetchRepoOverview and, if withCacheBuster is unused elsewhere, remove the
withCacheBuster helper to keep the file clean.

---

Outside diff comments:
In `@apps/site/public/touchai-intro-en/touchai-components.html`:
- Around line 1651-1702: typePromptThenSubmit() can run after the demo has
already started and restart the sequence mid-animation; guard its work by
checking the same start state used elsewhere: at the top of
typePromptThenSubmit() and immediately before enqueuing/performing each timed
step (inside typeNextPromptCharacter and before the submitButton.click callback)
verify hasStarted is false (or that document.body does not contain
'is-answering'); if the demo has started, cancel timers (promptTypingTimer) and
return early. Reference symbols: typePromptThenSubmit, typeNextPromptCharacter,
promptTypingTimer, submitButton.click, hasStarted, showAnswer, submitPrompt.

In `@apps/site/public/touchai-intro/touchai-components.html`:
- Around line 1722-1770: Reset the hasReceivedScrollProgress flag when switching
back to manual playback: set hasReceivedScrollProgress = false inside
showAnswer() (so any manual showAnswer path clears external-scroll state) and
also set hasReceivedScrollProgress = false at the start of
typePromptThenSubmit() (immediately after setScrollDrivenState(false)) so the
typed/manual entry path likewise clears the flag.

In `@apps/site/src/scripts/component-demo-runtime.ts`:
- Around line 595-614: When attaching a new 'message' handler, detect if this is
the first handler (i.e., messageHandlers.size === 0 before adding) and, if so,
immediately flush the queued/pending messages the same way applyDocument() does
so stranded progress/reset messages are delivered; implement by calling the
existing queue-drain logic (reuse the function or block used in applyDocument()
that processes the pending message queue) right after registering
wrappedMessageHandler and updating host.dataset.touchaiMessageHandlerCount so
queued messages are not left stranded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6da7003b-220a-4cf4-a68c-f1e64db562cc

📥 Commits

Reviewing files that changed from the base of the PR and between 36044b6 and e68e418.

⛔ Files ignored due to path filters (4)
  • apps/site/public/apple-touch-icon.png is excluded by !**/*.png
  • apps/site/public/icon-192.png is excluded by !**/*.png
  • apps/site/public/icon-512.png is excluded by !**/*.png
  • apps/site/public/seo-card.png is excluded by !**/*.png
📒 Files selected for processing (20)
  • apps/site/.env.example
  • apps/site/astro.config.mjs
  • apps/site/public/demo-utils/touchai-lite-math.js
  • apps/site/public/demo-utils/touchai-lite-renderer.js
  • apps/site/public/feature-reminder-en/touchai-components.html
  • apps/site/public/feature-reminder/touchai-components.html
  • apps/site/public/feature-solver-en/touchai-components.html
  • apps/site/public/feature-solver/touchai-components.html
  • apps/site/public/feature-work-organizer-en/touchai-components.html
  • apps/site/public/feature-work-organizer/touchai-components.html
  • apps/site/public/robots.txt
  • apps/site/public/site.webmanifest
  • apps/site/public/touchai-intro-en/touchai-components.html
  • apps/site/public/touchai-intro/touchai-components.html
  • apps/site/src/components/ComponentDemo.astro
  • apps/site/src/content.config.ts
  • apps/site/src/content/docs/404.md
  • apps/site/src/content/docs/getting-started.md
  • apps/site/src/pages/index.astro
  • apps/site/src/scripts/component-demo-runtime.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (rust)
🧰 Additional context used
🪛 ESLint
apps/site/src/content.config.ts

[error] 1-3: Run autofix to sort these imports!

(simple-import-sort/imports)

apps/site/public/demo-utils/touchai-lite-renderer.js

[error] 26-29: Replace ⏎····················/\*\*([^*]+)\*\*/g,⏎····················'<strong>$1</strong>'⏎················ with /\*\*([^*]+)\*\*/g,·'<strong>$1</strong>'

(prettier/prettier)


[error] 51-53: Replace ⏎····················.map((item)·=>·

  • ${renderInline(item)}
  • )⏎···················· with .map((item)·=>·
  • ${renderInline(item)}
  • )

    (prettier/prettier)

    apps/site/public/demo-utils/touchai-lite-math.js

    [error] 29-29: 'error' is defined but never used.

    (@typescript-eslint/no-unused-vars)


    [error] 29-29: Empty block statement.

    (no-empty)


    [error] 208-210: Replace ⏎············?·'formula-module'⏎··········· with ·?·'formula-module'

    (prettier/prettier)


    [error] 239-242: Replace ⏎····················new·RegExp(<p·class="paragraph-node">${token}

    ,·'g'),⏎····················replacement⏎················ with new·RegExp(<p·class="paragraph-node">${token}

    ,·'g'),·replacement

    (prettier/prettier)

    apps/site/src/scripts/component-demo-runtime.ts

    [error] 527-528: Delete ⏎·······

    (prettier/prettier)

    🔇 Additional comments (36)
    apps/site/astro.config.mjs (1)

    13-13: LGTM!

    apps/site/src/content.config.ts (1)

    5-10: LGTM!

    apps/site/public/robots.txt (1)

    1-4: LGTM!

    apps/site/public/site.webmanifest (1)

    1-29: LGTM!

    apps/site/src/content/docs/404.md (1)

    1-7: LGTM!

    apps/site/src/content/docs/getting-started.md (1)

    1-29: LGTM!

    apps/site/src/pages/index.astro (16)

    117-226: LGTM!


    291-301: LGTM!


    842-965: LGTM!


    1491-1548: LGTM!


    1919-1929: LGTM!


    2053-2076: LGTM!


    2089-2097: LGTM!

    Also applies to: 2138-2146, 2186-2194, 2232-2240


    2323-2328: LGTM!


    2481-2565: LGTM!


    2567-2606: LGTM!


    2815-2992: LGTM!


    3210-3213: LGTM!


    3257-3291: LGTM!


    3379-3474: LGTM!


    3476-3544: LGTM!


    3554-3588: LGTM!

    apps/site/.env.example (1)

    15-17: LGTM!

    apps/site/public/feature-work-organizer/touchai-components.html (7)

    1469-1489: Same issue as the English version: verify wheel forwarding target.

    This has the same window.scrollBy vs window.parent.scrollBy question flagged in the English variant.


    1563-1567: Same indentation issue as the English version.

    Lines 1565-1566 are under-indented inside the if block.


    86-88: LGTM!

    Also applies to: 155-168, 470-476, 496-515, 760-760


    1116-1116: LGTM!

    Also applies to: 1130-1130, 1148-1148


    1165-1189: LGTM!


    1297-1324: LGTM!


    1229-1229: LGTM!

    Also applies to: 1357-1357, 1368-1439, 1461-1467, 1501-1528, 1590-1650, 1661-1769

    apps/site/public/feature-work-organizer-en/touchai-components.html (6)

    86-88: LGTM!

    Also applies to: 155-168, 470-476, 496-515, 760-760


    1118-1118: LGTM!

    Also applies to: 1132-1132, 1150-1150


    1167-1191: LGTM!


    1299-1326: LGTM!


    1231-1231: LGTM!

    Also applies to: 1359-1359, 1370-1441, 1463-1469, 1503-1534, 1596-1656, 1667-1774


    1471-1491: Wheel forwarding target is correct (window.scrollBy), no iframe involved.

    The demos are mounted into the page by apps/site/src/scripts/component-demo-runtime.ts via doc.body.cloneNode(true) + host.replaceChildren(...) on the touchai-component-demo element (not an iframe). So window.scrollBy(0, nextDeltaY) scrolls the same document that GSAP/ScrollTrigger uses, making window.parent.scrollBy unnecessary here.

    			> Likely an incorrect or invalid review comment.
    

    Comment thread apps/site/public/demo-utils/touchai-lite-math.js Outdated
    Comment thread apps/site/public/feature-reminder-en/touchai-components.html
    Comment thread apps/site/public/feature-reminder/touchai-components.html Outdated
    Comment thread apps/site/public/feature-reminder/touchai-components.html
    Comment thread apps/site/public/feature-solver-en/touchai-components.html
    Comment thread apps/site/src/content.config.ts Outdated
    Comment thread apps/site/src/pages/index.astro Outdated
    Comment thread apps/site/src/pages/index.astro Outdated
    Comment thread apps/site/src/pages/index.astro Outdated
    Comment thread apps/site/src/pages/index.astro Outdated
    @hiqiancheng hiqiancheng changed the title fix(site): refresh landing demos and repo stats feat(site): refresh landing demos, stats, and metadata Jun 16, 2026

    @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.

    Actionable comments posted: 2

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (6)
    apps/site/public/touchai-intro-en/touchai-components.html (2)

    2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    English demo pages still expose Chinese document/ARIA metadata. The shared root cause is that the *-en demos updated visible content to English but kept Chinese language and assistive labels.

    • apps/site/public/touchai-intro-en/touchai-components.html#L2-L2: change the document language from zh-CN to en.
    • apps/site/public/touchai-intro-en/touchai-components.html#L1071-L1154: localize toolbar, prompt, copy, input, and send ARIA labels to English.
    • apps/site/public/feature-reminder-en/touchai-components.html#L2-L2: change the document language from zh-CN to en.
    • apps/site/public/feature-reminder-en/touchai-components.html#L1147-L1232: localize toolbar, prompt, copy, input, and send ARIA labels to English.
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/touchai-intro-en/touchai-components.html` at line 2, English
    demo pages contain Chinese language metadata and ARIA labels that must be
    localized. Fix this across four locations: (1) In
    apps/site/public/touchai-intro-en/touchai-components.html at line 2, change the
    html lang attribute from "zh-CN" to "en"; (2) In the same file at lines
    1071-1154, localize all ARIA labels and descriptive text for the toolbar,
    prompt, copy, input, and send button elements from Chinese to English; (3) In
    apps/site/public/feature-reminder-en/touchai-components.html at line 2, change
    the html lang attribute from "zh-CN" to "en"; (4) In the same feature-reminder
    file at lines 1147-1232, localize all ARIA labels and descriptive text for the
    toolbar, prompt, copy, input, and send button elements from Chinese to English.
    

    1483-1503: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    Wheel forwarding blocks browser zoom gestures across demos. Each forwardWheelToPage() prevents default for ctrlKey/metaKey wheel events before checking whether scroll-driven forwarding is active.

    • apps/site/public/touchai-intro-en/touchai-components.html#L1483-L1503: return without preventDefault() for modifier wheel events.
    • apps/site/public/touchai-intro/touchai-components.html#L1554-L1574: return without preventDefault() for modifier wheel events.
    • apps/site/public/feature-reminder-en/touchai-components.html#L1589-L1609: return without preventDefault() for modifier wheel events.
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/touchai-intro-en/touchai-components.html` around lines 1483
    - 1503, Remove the event.preventDefault() call from the ctrlKey/metaKey modifier
    check in the forwardWheelToPage() function at all three affected files, allowing
    the browser's default zoom gesture to proceed instead of being blocked. At
    apps/site/public/touchai-intro-en/touchai-components.html lines 1483-1503
    (anchor), change the first if block to return without preventDefault when
    modifier keys are detected. Apply the same change at the sibling locations in
    apps/site/public/touchai-intro/touchai-components.html lines 1554-1574 and
    apps/site/public/feature-reminder-en/touchai-components.html lines 1589-1609, as
    they contain identical problematic code.
    
    apps/site/public/feature-reminder/touchai-components.html (1)

    1652-1699: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    Clear aria-busy after scroll progress renders a completed answer.

    Each handler sets aria-busy before answerProgress is known, then the !hasStarted branch can force it back to true. If the first progress update lands at or past the completion threshold, the response is fully rendered but remains busy.

    • apps/site/public/feature-reminder/touchai-components.html#L1652-L1699: set the final aria-busy value after answerProgress is computed, clearing it when answerProgress >= 1.
    • apps/site/public/feature-solver-en/touchai-components.html#L1959-L2005: apply the same post-answerProgress busy-state update.
    • apps/site/public/feature-solver/touchai-components.html#L2045-L2091: apply the same post-answerProgress busy-state update.
    Suggested pattern
    -                    response.setAttribute(
    -                        'aria-busy',
    -                        hasReceivedScrollProgress ? 'true' : 'false'
    -                    );
                         externalScrollProgress = scrubProgress;
                         window.clearTimeout(typingTimer);
                         window.clearTimeout(promptTypingTimer);
    @@
                         const answerProgress = Math.max(0, Math.min(1, (scrubProgress - 0.18) / 0.5));
                         const scrollProgress = Math.max(0, Math.min(1, (scrubProgress - 0.66) / 0.34));
    +                    const responseIsBusy = hasReceivedScrollProgress && answerProgress < 1;
    @@
                         setResponseProgress(answerProgress);
    +                    response.setAttribute('aria-busy', responseIsBusy ? 'true' : 'false');
                         syncMathLineGeometry();
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-reminder/touchai-components.html` around lines 1652
    - 1699, The `aria-busy` attribute is being set in the `!hasStarted` branch
    before `answerProgress` is computed, which causes a fully rendered response to
    incorrectly remain marked as busy if the first progress update arrives at or
    past the completion threshold. Move the final `aria-busy` update to occur after
    `answerProgress` is calculated (after the line computing `const answerProgress =
    Math.max(0, Math.min(1, (scrubProgress - 0.18) / 0.5))`) and set it to 'false'
    when `answerProgress >= 1` to properly reflect the completion state. Apply this
    same fix at three locations:
    apps/site/public/feature-reminder/touchai-components.html lines 1652-1699
    (anchor), apps/site/public/feature-solver-en/touchai-components.html lines
    1959-2005 (sibling), and apps/site/public/feature-solver/touchai-components.html
    lines 2045-2091 (sibling).
    
    apps/site/src/pages/index.astro (3)

    2966-2970: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Respect the GitHub stats cache before refetching.

    After loading stored stats, Line 2970 always calls refreshRepoStats({ force: true }), bypassing the request gap and making fresh-cache page loads hit GitHub twice anyway. Gate the initial refresh on the stored updatedAt.

    Suggested fix
                     if (storedRepoStats) Object.assign(repoStats, storedRepoStats);
                     renderRepoStats(repoStats);
    -                refreshRepoStats({ force: true });
    +                if (shouldRefreshRepoStats(REPO_STATS_REFRESH_INTERVAL)) {
    +                    refreshRepoStats({ force: true });
    +                }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` around lines 2966 - 2970, The call to
    refreshRepoStats with force: true on line 2970 always bypasses the GitHub stats
    cache and forces a fresh fetch from GitHub, even when recently cached data is
    available. Instead of unconditionally calling refreshRepoStats({ force: true }),
    check the updatedAt timestamp from the storedRepoStats and only trigger a
    refresh if the cache is stale based on an appropriate time threshold. This way,
    fresh-cache page loads will respect the existing cache gap and avoid making
    redundant GitHub API requests.
    

    1951-1951: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    Align the initial intro demo URL with the default language URL.

    Line 1951 loads ?v=7, but setLanguage('zh') immediately changes the same demo to ?v=8 on boot. That double-loads the hero demo for default-language visitors.

    Suggested fix
    -                            src="/touchai-intro/touchai-components.html?v=7"
    +                            src="/touchai-intro/touchai-components.html?v=8"

    Also applies to: 2602-2606

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` at line 1951, The touchai-components.html
    demo is initially loaded with version 7 but immediately updated to version 8
    when setLanguage is called on boot, causing an unnecessary double-load for
    default-language visitors. Update the initial src attribute for the
    touchai-components.html iframe to use version 8 instead of version 7, and ensure
    any other similar demo component URLs (including those around lines 2602-2606)
    are also updated to use the same consistent version parameter to prevent
    duplicate loads.
    

    2507-2508: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Force progress sync after embedded demo reloads.

    lastDispatchedDemoProgress is keyed by the host frame, so it survives frame.reload(). The load/ready/replay paths resync without force; when the progress value is unchanged, Line 2508 drops the message and the newly loaded iframe stays at its default state.

    Suggested fix
    -                    solverFrame.addEventListener('load', () => {
    +                    solverFrame.addEventListener('load', () => {
                             syncFeatureDemoProgress(
                                 solverFrame,
    -                            featureProgressByFrame.get(solverFrame) ?? 0
    +                            featureProgressByFrame.get(solverFrame) ?? 0,
    +                            true
                             );
                         });
    -                    workOrganizerFrame.addEventListener('load', () => {
    +                    workOrganizerFrame.addEventListener('load', () => {
                             syncFeatureDemoProgress(
                                 workOrganizerFrame,
    -                            featureProgressByFrame.get(workOrganizerFrame) ?? 0
    +                            featureProgressByFrame.get(workOrganizerFrame) ?? 0,
    +                            true
                             );
                         });
    -                    reminderFrame.addEventListener('load', () => {
    +                    reminderFrame.addEventListener('load', () => {
                             syncFeatureDemoProgress(
                                 reminderFrame,
    -                            featureProgressByFrame.get(reminderFrame) ?? 0
    +                            featureProgressByFrame.get(reminderFrame) ?? 0,
    +                            true
                             );
                         });
                     const replayEmbeddedComponent = async (frame) => {
                         const progress = featureProgressByFrame.get(frame) ?? 0;
                         await Promise.resolve(frame.reload?.());
    -                    syncFeatureDemoProgress(frame, progress);
    -                    window.setTimeout(() => syncFeatureDemoProgress(frame, progress), 120);
    -                    window.setTimeout(() => syncFeatureDemoProgress(frame, progress), 360);
    +                    syncFeatureDemoProgress(frame, progress, true);
    +                    window.setTimeout(() => syncFeatureDemoProgress(frame, progress, true), 120);
    +                    window.setTimeout(() => syncFeatureDemoProgress(frame, progress, true), 360);
                     };

    For the intro demo, pass a force flag through the ready/load refresh path:

    -                        const syncComponent = (progress) => {
    -                            queueDemoProgress(introDemo, 'touchai-set-progress', progress);
    +                        const syncComponent = (progress, force = false) => {
    +                            queueDemoProgress(introDemo, 'touchai-set-progress', progress, force);
                             };
    -                        const refreshPinnedIntro = () => {
    +                        const refreshPinnedIntro = (force = false) => {
                                 ScrollTrigger.refresh();
    -                            syncComponent(componentTrigger?.progress ?? 0);
    +                            syncComponent(componentTrigger?.progress ?? 0, force);
                             };
    -                                if (isActive) refreshPinnedIntro();
    +                                if (isActive) refreshPinnedIntro(true);

    Also applies to: 3121-3125, 3141-3145, 3161-3165, 3177-3182, 3381-3392

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` around lines 2507 - 2508, The issue is that
    lastDispatchedDemoProgress survives frame reloads due to being keyed by the host
    frame, so when load/ready/replay paths resync without a force flag, the
    condition on line 2508 skips dispatching the progress message if the value is
    unchanged, leaving newly loaded iframes at their default state. To fix this,
    pass a force flag as true through the ready/load/replay paths for the intro demo
    to ensure the progress message is always dispatched after a frame reload, even
    when the progress value hasn't changed. Identify all calls that trigger these
    resync paths and modify them to include force set to true so the condition
    !force && lastProgress === snappedProgress evaluates correctly and allows the
    message through.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@apps/site/src/pages/index.astro`:
    - Line 2295: The footer docs link element with href={docsUrl} and
    data-i18n="footer.docs" is missing the data-analytics-event attribute that is
    present on neighboring footer links, causing analytics tracking to skip this
    navigation. Add the data-analytics-event attribute to the docs footer link
    anchor tag to ensure clicks on this link are tracked consistently with other
    footer navigation elements.
    - Around line 1915-1917: The span element with data-i18n="hero.heading"
    references a translation key that does not exist in the translation maps for any
    locale. This causes the screen-reader text to remain in Chinese even when
    switching to English. Add the hero.heading key to both locale translation maps
    (the English and Chinese translation files) with appropriate values so that the
    screen-reader content respects the current language setting.
    
    ---
    
    Outside diff comments:
    In `@apps/site/public/feature-reminder/touchai-components.html`:
    - Around line 1652-1699: The `aria-busy` attribute is being set in the
    `!hasStarted` branch before `answerProgress` is computed, which causes a fully
    rendered response to incorrectly remain marked as busy if the first progress
    update arrives at or past the completion threshold. Move the final `aria-busy`
    update to occur after `answerProgress` is calculated (after the line computing
    `const answerProgress = Math.max(0, Math.min(1, (scrubProgress - 0.18) / 0.5))`)
    and set it to 'false' when `answerProgress >= 1` to properly reflect the
    completion state. Apply this same fix at three locations:
    apps/site/public/feature-reminder/touchai-components.html lines 1652-1699
    (anchor), apps/site/public/feature-solver-en/touchai-components.html lines
    1959-2005 (sibling), and apps/site/public/feature-solver/touchai-components.html
    lines 2045-2091 (sibling).
    
    In `@apps/site/public/touchai-intro-en/touchai-components.html`:
    - Line 2: English demo pages contain Chinese language metadata and ARIA labels
    that must be localized. Fix this across four locations: (1) In
    apps/site/public/touchai-intro-en/touchai-components.html at line 2, change the
    html lang attribute from "zh-CN" to "en"; (2) In the same file at lines
    1071-1154, localize all ARIA labels and descriptive text for the toolbar,
    prompt, copy, input, and send button elements from Chinese to English; (3) In
    apps/site/public/feature-reminder-en/touchai-components.html at line 2, change
    the html lang attribute from "zh-CN" to "en"; (4) In the same feature-reminder
    file at lines 1147-1232, localize all ARIA labels and descriptive text for the
    toolbar, prompt, copy, input, and send button elements from Chinese to English.
    - Around line 1483-1503: Remove the event.preventDefault() call from the
    ctrlKey/metaKey modifier check in the forwardWheelToPage() function at all three
    affected files, allowing the browser's default zoom gesture to proceed instead
    of being blocked. At apps/site/public/touchai-intro-en/touchai-components.html
    lines 1483-1503 (anchor), change the first if block to return without
    preventDefault when modifier keys are detected. Apply the same change at the
    sibling locations in apps/site/public/touchai-intro/touchai-components.html
    lines 1554-1574 and apps/site/public/feature-reminder-en/touchai-components.html
    lines 1589-1609, as they contain identical problematic code.
    
    In `@apps/site/src/pages/index.astro`:
    - Around line 2966-2970: The call to refreshRepoStats with force: true on line
    2970 always bypasses the GitHub stats cache and forces a fresh fetch from
    GitHub, even when recently cached data is available. Instead of unconditionally
    calling refreshRepoStats({ force: true }), check the updatedAt timestamp from
    the storedRepoStats and only trigger a refresh if the cache is stale based on an
    appropriate time threshold. This way, fresh-cache page loads will respect the
    existing cache gap and avoid making redundant GitHub API requests.
    - Line 1951: The touchai-components.html demo is initially loaded with version 7
    but immediately updated to version 8 when setLanguage is called on boot, causing
    an unnecessary double-load for default-language visitors. Update the initial src
    attribute for the touchai-components.html iframe to use version 8 instead of
    version 7, and ensure any other similar demo component URLs (including those
    around lines 2602-2606) are also updated to use the same consistent version
    parameter to prevent duplicate loads.
    - Around line 2507-2508: The issue is that lastDispatchedDemoProgress survives
    frame reloads due to being keyed by the host frame, so when load/ready/replay
    paths resync without a force flag, the condition on line 2508 skips dispatching
    the progress message if the value is unchanged, leaving newly loaded iframes at
    their default state. To fix this, pass a force flag as true through the
    ready/load/replay paths for the intro demo to ensure the progress message is
    always dispatched after a frame reload, even when the progress value hasn't
    changed. Identify all calls that trigger these resync paths and modify them to
    include force set to true so the condition !force && lastProgress ===
    snappedProgress evaluates correctly and allows the message through.
    
    🪄 Autofix (Beta)

    Fix all unresolved CodeRabbit comments on this PR:

    • Push a commit to this branch (recommended)
    • Create a new PR with the fixes

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: efc4e3bc-1745-43dc-8e3a-ea773d8e2e42

    📥 Commits

    Reviewing files that changed from the base of the PR and between e68e418 and 278994e.

    📒 Files selected for processing (14)
    • apps/site/public/demo-utils/touchai-lite-math.js
    • apps/site/public/demo-utils/touchai-lite-renderer.js
    • apps/site/public/feature-reminder-en/touchai-components.html
    • apps/site/public/feature-reminder/touchai-components.html
    • apps/site/public/feature-solver-en/touchai-components.html
    • apps/site/public/feature-solver/touchai-components.html
    • apps/site/public/feature-work-organizer-en/touchai-components.html
    • apps/site/public/feature-work-organizer/touchai-components.html
    • apps/site/public/site.webmanifest
    • apps/site/public/touchai-intro-en/touchai-components.html
    • apps/site/public/touchai-intro/touchai-components.html
    • apps/site/src/content.config.ts
    • apps/site/src/pages/index.astro
    • apps/site/src/scripts/component-demo-runtime.ts
    📜 Review details
    ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
    • GitHub Check: CodeQL (rust)
    🧰 Additional context used
    🪛 ast-grep (0.43.0)
    apps/site/public/demo-utils/touchai-lite-math.js

    [warning] 239-239: Detects non-literal values in regular expressions
    Context: new RegExp(token, 'g')
    Note: Security best practice.

    (detect-non-literal-regexp)

    🪛 HTMLHint (1.9.2)
    apps/site/public/feature-solver-en/touchai-components.html

    [warning] 1515-1515: No matching [ label ] tag found.

    (input-requires-label)

    🔇 Additional comments (21)
    apps/site/src/content.config.ts (2)

    1-3: Import sort order is compliant with simple-import-sort rules.

    Verification confirms that the import order on lines 1–3 passes the simple-import-sort/imports linting rule without errors.


    3-3: The import path import { defineCollection } from 'astro:content' is correct for Astro 6.3.1 and aligns with the official Astro Content Layer API documentation. No action needed.

    apps/site/public/site.webmanifest (1)

    12-27: Icon files are properly committed and accessible.

    The manifest references three icon entries (/icon-192.png and /icon-512.png, plus a maskable variant), and both files exist in apps/site/public/ with appropriate sizes (18K and 75K respectively).

    apps/site/public/demo-utils/touchai-lite-math.js (1)

    2-6: Existing fallback escaping issue still applies.

    This code still captures TouchAILiteRenderer once and falls back to raw String(value) when it is unavailable, matching the previous review finding.

    Also applies to: 229-231

    apps/site/public/feature-reminder-en/touchai-components.html (1)

    1488-1490: Existing tool-card reset issue still applies.

    resetVisibleBlocks() still omits .tool-call-list and .tool-call, matching the previous review finding.

    apps/site/public/demo-utils/touchai-lite-renderer.js (1)

    16-28: LGTM!

    Also applies to: 45-49

    apps/site/src/scripts/component-demo-runtime.ts (1)

    499-529: LGTM!

    apps/site/public/feature-reminder/touchai-components.html (2)

    496-500: Duplicate: mobile completion still overrides the small-screen height.

    This is the same unresolved selector-specificity issue from the previous review.


    1587-1607: Duplicate: wheel input is still swallowed inside the iframe.

    The handler still calls preventDefault() and window.scrollBy() in the iframe instead of reaching the host page.

    apps/site/public/feature-solver-en/touchai-components.html (2)

    426-430: Duplicate: mobile completion still overrides the small-screen height.

    This is the same unresolved selector-specificity issue from the previous review.


    1890-1910: Duplicate: wheel input is still swallowed inside the iframe.

    The handler still calls preventDefault() and window.scrollBy() in the iframe instead of reaching the host page.

    apps/site/public/feature-solver/touchai-components.html (2)

    426-430: Duplicate: mobile completion still overrides the small-screen height.

    This is the same unresolved selector-specificity issue from the previous review.


    1980-2000: Duplicate: wheel input is still swallowed inside the iframe.

    The handler still calls preventDefault() and window.scrollBy() in the iframe instead of reaching the host page.

    apps/site/public/feature-work-organizer-en/touchai-components.html (4)

    1118-1123: LGTM!

    Also applies to: 1137-1144


    1552-1555: LGTM!

    Also applies to: 1586-1587


    1739-1741: LGTM!


    1790-1792: LGTM!

    apps/site/public/feature-work-organizer/touchai-components.html (4)

    1116-1121: LGTM!

    Also applies to: 1135-1142


    1546-1549: LGTM!

    Also applies to: 1580-1581


    1734-1736: LGTM!


    1785-1787: LGTM!

    Comment thread apps/site/src/pages/index.astro
    Comment thread apps/site/src/pages/index.astro 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.

    Actionable comments posted: 4

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (1)
    apps/site/src/components/ComponentDemo.astro (1)

    193-418: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

    Consider extracting shared CSS to reduce duplication.

    The hostCss block here (~220 lines) closely mirrors hostCssFor in component-demo-runtime.ts. Both must stay synchronized for SSR and client-mount behavior to match. Future CSS changes risk drift if applied to only one file.

    A shared CSS template (imported by both files) or a single source of truth would reduce this maintenance burden.

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/components/ComponentDemo.astro` around lines 193 - 418, The
    hostCss variable in ComponentDemo.astro is duplicated with similar CSS code
    named hostCssFor in component-demo-runtime.ts, creating a maintenance risk where
    changes to one file may not be synchronized with the other. Extract the shared
    CSS template into a single source file (such as a shared utility module), and
    update both ComponentDemo.astro and component-demo-runtime.ts to import and use
    this common CSS template instead of maintaining separate copies. This ensures
    that SSR and client-mount behavior remain synchronized and reduces the burden of
    keeping the CSS logic in sync across multiple files.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@apps/site/scripts/check-homepage-regressions.mjs`:
    - Around line 59-68: The validation logic in the while loop uses an arbitrary
    and fragile 1200-character window to search for CSS rules. Replace the current
    approach that slices a fixed 1200-character fragment and checks if all selectors
    and properties appear within it. Instead, check each selector individually by
    searching the full source for patterns that verify each selector contains both
    the min-height and height properties with the required !important flags. This
    makes the validation robust to CSS formatting variations, comments, or
    additional whitespace that might exceed the current window size.
    - Around line 41-45: The regex pattern in the matchAll call for `.github-stats`
    has the same limitation as the feature card checks: the pattern
    `/\.github-stats\s*\{([^{}]*)\}/g` uses `[^{}]*` which stops at the first
    closing brace and won't correctly handle nested CSS rules. Fix the regex pattern
    to properly handle nested braces, similar to the approach used for the feature
    card checks mentioned in lines 32-39, so that the check for `repeat(3` inside
    the github-stats rule is robust and works correctly even with nested rule
    structures.
    - Around line 8-12: The readSiteFile function calls that read the homepage,
    ComponentDemo, and runtime files lack error handling, causing the script to
    crash with generic Node.js errors if any file is missing or unreadable. Wrap
    each readSiteFile invocation in a try-catch block to catch potential errors, and
    provide clear diagnostic messages indicating which specific file failed to read
    and the reason for the failure, making it easier to debug issues with missing or
    moved source files.
    - Around line 32-39: The regex pattern in the loop that processes homepage CSS
    rules does not properly handle CSS declarations nested inside media query blocks
    because the pattern `[^{}]*` stops at the first closing brace, causing media
    query overrides to be missed by the regression check. Replace the current
    regex-based approach with a CSS parser that can traverse nested structures
    properly, or modify the matching logic to separately search for the problematic
    property patterns (height: auto and aspect-ratio: 760 / 657) across the entire
    stylesheet content rather than relying on bracket-matched blocks. This will
    ensure the regression detection at the fail() call captures violations
    regardless of whether they appear in top-level rules or media query overrides.
    
    ---
    
    Outside diff comments:
    In `@apps/site/src/components/ComponentDemo.astro`:
    - Around line 193-418: The hostCss variable in ComponentDemo.astro is duplicated
    with similar CSS code named hostCssFor in component-demo-runtime.ts, creating a
    maintenance risk where changes to one file may not be synchronized with the
    other. Extract the shared CSS template into a single source file (such as a
    shared utility module), and update both ComponentDemo.astro and
    component-demo-runtime.ts to import and use this common CSS template instead of
    maintaining separate copies. This ensures that SSR and client-mount behavior
    remain synchronized and reduces the burden of keeping the CSS logic in sync
    across multiple files.
    
    🪄 Autofix (Beta)

    Fix all unresolved CodeRabbit comments on this PR:

    • Push a commit to this branch (recommended)
    • Create a new PR with the fixes

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: 87dbf48e-32f3-4d5e-83ca-013f738de138

    📥 Commits

    Reviewing files that changed from the base of the PR and between 278994e and 8ec6ddd.

    📒 Files selected for processing (5)
    • apps/site/package.json
    • apps/site/scripts/check-homepage-regressions.mjs
    • apps/site/src/components/ComponentDemo.astro
    • apps/site/src/pages/index.astro
    • apps/site/src/scripts/component-demo-runtime.ts
    📜 Review details
    ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
    • GitHub Check: CodeQL (rust)
    🔇 Additional comments (22)
    apps/site/src/scripts/component-demo-runtime.ts (2)

    152-159: LGTM!


    161-169: LGTM!

    apps/site/src/components/ComponentDemo.astro (3)

    309-316: LGTM!


    318-326: LGTM!


    433-434: LGTM!

    apps/site/package.json (1)

    10-10: LGTM!

    apps/site/scripts/check-homepage-regressions.mjs (3)

    13-21: LGTM!


    23-30: LGTM!


    78-83: LGTM!

    apps/site/src/pages/index.astro (13)

    23-43: LGTM!

    The build-time stats fetching logic correctly targets only stars and forks from the GitHub API. The headers and response handling are appropriate.

    Note: The empty catch block diagnostic logging was flagged in a prior review.


    2279-2279: Add data-analytics-event to footer docs link.

    This was flagged in a prior review: the footer docs link is missing the analytics tracking attribute that neighboring links have (open_official_site, open_repository).


    1905-1907: Missing hero.heading translation key.

    This was flagged in a prior review: the data-i18n="hero.heading" attribute references a key that doesn't exist in either the zh or en translation maps. The screen-reader H1 will retain its Chinese content when switching to English.


    2884-2900: LGTM!

    The simplified single-fetch approach for repository stats is cleaner. The validation at line 2917 correctly checks only the stats being fetched (stars, forks).

    Note: The redundant cache busting (withCacheBuster + cache: 'no-store') was flagged in a prior review.


    2641-2695: LGTM!

    The analytics implementation is well-structured: sendBeacon with fetch fallback, pageview deduplication via hasTrackedPageView, and click tracking delegation. Error handling with empty catch for fire-and-forget is appropriate here.

    Note: Privacy documentation and endpoint validation were flagged in prior reviews.


    835-843: LGTM!

    The CSS changes introduce a consistent pattern across all feature cards: dynamic height via --feature-demo-height with a minimal 35px fallback, and overflow: visible to allow the scaled iframe to extend beyond container bounds. This works correctly with the absolute-positioned, scaled frames using transform-origin: top left.

    Also applies to: 877-885, 919-920, 927-927


    1458-1461: LGTM!

    The responsive breakpoints correctly mirror the desktop pattern for dynamic heights, and the 2-column grid layout for GitHub stats at narrow viewports is appropriate given there are now only 2 stats displayed.

    Also applies to: 1677-1679, 1699-1699


    2043-2060: ⚡ Quick win

    PR description states "Open issues" display is corrected, but implementation removes it entirely.

    The PR objectives mention: "Correct the 'Open issues' display to count only open issues, excluding pull requests (verified locally as: 42 Stars / 24 Forks / 72 Open issues)." However, the implementation removes the issues stat completely—only Stars and Forks are displayed.

    Please confirm whether this is intentional (and update the PR description accordingly) or if the issues stat should be restored with the corrected count.


    3340-3436: LGTM!

    The scroll trigger setup demonstrates proper resource management:

    • Each setup function returns a cleanup callback
    • gsap.matchMedia correctly orchestrates viewport-specific triggers with cleanup on breakpoint change
    • Cleanup cancels queued progress and sends touchai-clear-scroll-driven to iframes

    Also applies to: 3438-3508, 3518-3552


    2442-2521: LGTM!

    The queued progress system is well-designed:

    • WeakMap for frame-keyed state prevents memory leaks
    • snapDemoProgress provides sensible quantization (1/240 increments)
    • RAF-based batching with deduplication avoids redundant postMessage calls
    • Proper cleanup via cancelQueuedDemoProgress

    2523-2635: LGTM!

    The language switching implementation correctly updates localized head nodes, reloads demo iframes with language-specific URLs, and restores scroll progress. The setTimeout cascade in restoreFeatureDemoProgress is a pragmatic workaround for iframe load timing uncertainty.


    2966-3050: LGTM!

    The remaining client-side code demonstrates solid practices:

    • Glimm shader initialization with proper WebGL fallback and error boundaries
    • Interactive click handlers with comprehensive pointer event cleanup
    • Replay card binding that prevents duplicate replays and correctly filters nested interactive elements
    • Passive scroll listeners and debounced resize handlers

    Also applies to: 3052-3087, 3149-3189, 3555-3570


    2707-2728: The .querySelector('.chat-panel') assumption is based on an incorrect iframe model.

    The original review comment assumes .chat-panel is inside an iframe and cannot be queried by the parent. However, the codebase shows that touchai-component-demo is a regular custom HTML element with inline content—not an iframe wrapper. Content is loaded asynchronously via applyDocument() (which calls host.replaceChildren(...)) and injected directly into the host element. The .chat-panel element exists as a direct descendant once loaded.

    The real issue is timing: syncFeatureFrameSizing() is called synchronously at page init (line 2964) before the feature frames' content has loaded, so frame.querySelector('.chat-panel') returns null on that initial call. However, this is mitigated by:

    1. The null-check fallback: panel?.offsetHeight || 56 (line 2710)
    2. The load event listeners registered at line 2960-2961, which trigger syncComponentBackground()syncFeatureFrameSizing() after content actually loads
    3. The guard at line 2720: if (!panel) return prevents setting up an incomplete observer

    The current code handles the async loading correctly—the initial null is benign, and the observer is properly set up when content becomes available. No code changes appear necessary.

    Comment thread apps/site/scripts/check-homepage-regressions.mjs Outdated
    Comment thread apps/site/scripts/check-homepage-regressions.mjs Outdated
    Comment thread apps/site/scripts/check-homepage-regressions.mjs Outdated
    Comment thread apps/site/scripts/check-homepage-regressions.mjs 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.

    Actionable comments posted: 1

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (4)
    apps/site/public/feature-reminder/touchai-components.html (1)

    1486-1492: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    Clear tool-card visibility during resets.

    The resetVisibleBlocks selector omits .tool-call-list and .tool-call. After the first reveal, scrubbing back or replaying leaves those cards visible from the start of the next run, since the CSS at lines 763-764 hides them only when they lack .is-visible.

    Suggested fix
     function resetVisibleBlocks() {
         response
    -        .querySelectorAll('p, h2, ul, li, .math-block, .response-divider')
    +        .querySelectorAll(
    +            'p, h2, ul, li, .tool-call-list, .tool-call, .math-block, .response-divider'
    +        )
             .forEach((block) => {
                 block.classList.remove('is-visible');
             });
     }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-reminder/touchai-components.html` around lines 1486
    - 1492, The resetVisibleBlocks function is missing `.tool-call-list` and
    `.tool-call` from its querySelectorAll selector, so these elements retain
    visibility from previous runs. Add `.tool-call-list` and `.tool-call` to the
    selector string in the resetVisibleBlocks function's querySelectorAll call so
    that the `.is-visible` class is removed from these elements during reset,
    allowing the CSS rules to properly hide them.
    
    apps/site/public/feature-work-organizer-en/touchai-components.html (3)

    496-500: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Keep the fixed complete height desktop-only.

    This selector is more specific than the mobile body.is-complete .chat-panel rule, so small screens can keep height: 657px during scroll-driven completion and overflow the embedded card.

    📱 Proposed CSS fix
    -            body.is-scroll-driven.is-complete .chat-panel {
    -                min-height: var(--window-min-height);
    -                max-height: var(--window-min-height);
    -                height: var(--window-min-height);
    -            }
    +            `@media` (min-width: 841px) {
    +                body.is-scroll-driven.is-complete .chat-panel {
    +                    min-height: var(--window-min-height);
    +                    max-height: var(--window-min-height);
    +                    height: var(--window-min-height);
    +                }
    +            }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html` around
    lines 496 - 500, The selector body.is-scroll-driven.is-complete .chat-panel is
    more specific than the mobile body.is-complete .chat-panel rule, causing the
    fixed height to apply incorrectly to small screens. Restrict this rule to
    desktop screens only by wrapping it in a media query (such as min-width based
    breakpoint) so that mobile devices continue to use the original height: 657px
    behavior during scroll-driven completion, preventing overflow of the embedded
    card.
    

    2-2: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Localize the English demo’s document language and ARIA labels.

    This English demo still declares lang="zh-CN" and exposes Chinese control labels such as the toolbar, composer, scroll button, and answer actions. Screen readers will use the wrong language context and announce mixed-language controls.

    🌐 Proposed localization fix
    -<html lang="zh-CN">
    +<html lang="en">
    ...
    -            <section class="chat-panel is-unified" aria-label="TouchAI 对话组件">
    -                <header class="panel-top toolbar-fade-overlay" aria-label="窗口工具栏">
    -                    <button class="toolbar-button" type="button" aria-label="新建会话">
    +            <section class="chat-panel is-unified" aria-label="TouchAI conversation demo">
    +                <header class="panel-top toolbar-fade-overlay" aria-label="Window toolbar">
    +                    <button class="toolbar-button" type="button" aria-label="New conversation">
    ...
    -                        <button class="scroll-bottom-button" type="button" aria-label="回到底部">
    +                        <button class="scroll-bottom-button" type="button" aria-label="Scroll to bottom">
    ...
    -                <footer class="composer" aria-label="输入框">
    -                    <form class="composer-form prompt-form" aria-label="输入框">
    +                <footer class="composer" aria-label="Message composer">
    +                    <form class="composer-form prompt-form" aria-label="Message composer">
    ...
    -                            aria-label="输入问题"
    +                            aria-label="Problem input"
    ...
    -                        <button class="submit-btn" type="submit" aria-label="发送">
    +                        <button class="submit-btn" type="submit" aria-label="Send">
    ...
    -          <div class="response-actions" aria-label="回答操作">
    -            <span class="thinking-dots" aria-label="正在思考">
    +          <div class="response-actions" aria-label="Answer actions">
    +            <span class="thinking-dots" aria-label="Thinking">
    ...
    -            <button class="action-button copy-answer" type="button" aria-label="复制回答">
    +            <button class="action-button copy-answer" type="button" aria-label="Copy answer">
    ...
    -            <button class="action-button replay-answer" type="button" aria-label="重新生成">
    +            <button class="action-button replay-answer" type="button" aria-label="Regenerate answer">

    Also applies to: 1068-1153, 1228-1239

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html` at line
    2, The HTML file's language declaration is set to Chinese (zh-CN) in the html
    element lang attribute at the root level, but this is an English demo. Change
    the lang attribute from "zh-CN" to "en" or "en-US" to properly declare the
    document language as English. Additionally, at lines 1068-1153 and 1228-1239,
    locate all ARIA labels, control labels, and text content for the toolbar,
    composer, scroll button, and answer actions that are currently in Chinese and
    translate them to English equivalents so that screen readers announce controls
    in the correct language and users see English text throughout the interface.
    

    1489-1509: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Forward wheel deltas to the host instead of scrolling the iframe.

    In scroll-driven mode this handler calls preventDefault() and then window.scrollBy(), which only scrolls the iframe document. The wheel input is swallowed instead of advancing the parent page.

    🖱️ Proposed forwarding fix
    -                    event.preventDefault();
    -                    window.scrollBy(0, nextDeltaY);
    +                    event.preventDefault();
    +                    try {
    +                        if (window.parent && window.parent !== window) {
    +                            window.parent.scrollBy(0, nextDeltaY);
    +                        } else {
    +                            window.scrollBy(0, nextDeltaY);
    +                        }
    +                    } catch (error) {
    +                        window.parent?.postMessage(
    +                            { type: 'touchai-forward-wheel', deltaY: nextDeltaY },
    +                            window.location.origin
    +                        );
    +                    }

    If you use the postMessage fallback, add the matching host-page handler for touchai-forward-wheel.

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html` around
    lines 1489 - 1509, The forwardWheelToPage function currently calls
    window.scrollBy which only scrolls the iframe document instead of forwarding
    wheel input to the parent page. Replace the window.scrollBy(0, nextDeltaY) call
    with a postMessage call that sends the wheel delta to the parent window using
    window.parent.postMessage with a message type of touchai-forward-wheel and the
    nextDeltaY value included in the message payload. Additionally, add a
    corresponding message handler on the host page that listens for the
    touchai-forward-wheel message type and applies the forwarded wheel delta to
    scroll the parent page.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@apps/site/scripts/check-homepage-regressions.mjs`:
    - Around line 150-152: The check in the conditional statement uses overly broad
    substring matching with html.includes('scrollProgress') which can produce false
    positives when scrollProgress appears in variable names, function names,
    comments, or strings unrelated to the problematic pattern. Replace the broad
    substring checks with more specific pattern matching that anchors to the exact
    problematic code patterns you want to reject, such as using a regular expression
    that matches scrollProgress only when used as a variable reference in the
    specific context that causes the regression (e.g., when used in an assignment or
    expression in the problematic way), rather than matching any occurrence of the
    substring anywhere in the HTML content.
    
    ---
    
    Outside diff comments:
    In `@apps/site/public/feature-reminder/touchai-components.html`:
    - Around line 1486-1492: The resetVisibleBlocks function is missing
    `.tool-call-list` and `.tool-call` from its querySelectorAll selector, so these
    elements retain visibility from previous runs. Add `.tool-call-list` and
    `.tool-call` to the selector string in the resetVisibleBlocks function's
    querySelectorAll call so that the `.is-visible` class is removed from these
    elements during reset, allowing the CSS rules to properly hide them.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html`:
    - Around line 496-500: The selector body.is-scroll-driven.is-complete
    .chat-panel is more specific than the mobile body.is-complete .chat-panel rule,
    causing the fixed height to apply incorrectly to small screens. Restrict this
    rule to desktop screens only by wrapping it in a media query (such as min-width
    based breakpoint) so that mobile devices continue to use the original height:
    657px behavior during scroll-driven completion, preventing overflow of the
    embedded card.
    - Line 2: The HTML file's language declaration is set to Chinese (zh-CN) in the
    html element lang attribute at the root level, but this is an English demo.
    Change the lang attribute from "zh-CN" to "en" or "en-US" to properly declare
    the document language as English. Additionally, at lines 1068-1153 and
    1228-1239, locate all ARIA labels, control labels, and text content for the
    toolbar, composer, scroll button, and answer actions that are currently in
    Chinese and translate them to English equivalents so that screen readers
    announce controls in the correct language and users see English text throughout
    the interface.
    - Around line 1489-1509: The forwardWheelToPage function currently calls
    window.scrollBy which only scrolls the iframe document instead of forwarding
    wheel input to the parent page. Replace the window.scrollBy(0, nextDeltaY) call
    with a postMessage call that sends the wheel delta to the parent window using
    window.parent.postMessage with a message type of touchai-forward-wheel and the
    nextDeltaY value included in the message payload. Additionally, add a
    corresponding message handler on the host page that listens for the
    touchai-forward-wheel message type and applies the forwarded wheel delta to
    scroll the parent page.
    
    🪄 Autofix (Beta)

    Fix all unresolved CodeRabbit comments on this PR:

    • Push a commit to this branch (recommended)
    • Create a new PR with the fixes

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: 67bcee92-177f-4ae5-a6c6-77cf395e1ee7

    📥 Commits

    Reviewing files that changed from the base of the PR and between 8ec6ddd and 7744256.

    📒 Files selected for processing (10)
    • apps/site/public/feature-reminder-en/touchai-components.html
    • apps/site/public/feature-reminder/touchai-components.html
    • apps/site/public/feature-solver-en/touchai-components.html
    • apps/site/public/feature-solver/touchai-components.html
    • apps/site/public/feature-work-organizer-en/touchai-components.html
    • apps/site/public/feature-work-organizer/touchai-components.html
    • apps/site/scripts/check-homepage-regressions.mjs
    • apps/site/src/components/ComponentDemo.astro
    • apps/site/src/pages/index.astro
    • apps/site/src/scripts/component-demo-runtime.ts
    📜 Review details
    ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
    • GitHub Check: CodeQL (rust)
    🔇 Additional comments (52)
    apps/site/src/pages/index.astro (4)

    3092-3108: LGTM!


    3114-3130: LGTM!


    3136-3152: LGTM!


    3460-3462: LGTM!

    apps/site/scripts/check-homepage-regressions.mjs (4)

    13-20: LGTM!


    55-69: LGTM!


    71-77: LGTM!


    110-135: LGTM!

    apps/site/public/feature-reminder-en/touchai-components.html (6)

    496-500: The is-scroll-driven.is-complete rule may override mobile height constraints.

    This selector has higher specificity than the mobile body.is-complete .chat-panel rule at lines 1133-1136. On short viewports, this reintroduces the fixed 657px height during scroll-driven completion.


    1486-1494: Clear tool-card visibility during resets.

    The resetVisibleBlocks function's selector is missing .tool-call-list and .tool-call. After the first reveal, scrubbing back or replaying leaves those cards visible from the start of the next run, since the CSS at lines 763-764 hides them only when they lack .is-visible.


    1593-1615: Wheel forwarding scrolls the iframe, not the parent page.

    After preventDefault(), window.scrollBy(0, nextDeltaY) at line 1614 scrolls the iframe document rather than forwarding the delta to the embedding page. In scroll-driven mode, this traps wheel input inside the demo instead of allowing the parent to handle page scrolling.

    Consider forwarding the delta via postMessage to let the parent handle scrolling:

    window.parent.postMessage({ type: 'touchai-wheel-delta', deltaY: nextDeltaY }, '*');

    1-50: LGTM!


    1195-1282: LGTM!


    1617-1905: LGTM!

    apps/site/public/feature-reminder/touchai-components.html (5)

    496-500: The is-scroll-driven.is-complete rule may override mobile height constraints.

    This issue was flagged in a previous review. The selector's specificity overrides the mobile body.is-complete .chat-panel rule, reintroducing the fixed 657px height on short viewports during scroll-driven completion.


    1593-1613: Wheel forwarding scrolls the iframe, not the parent page.

    This issue was flagged in a previous review. After preventDefault(), window.scrollBy() scrolls the iframe document rather than forwarding the delta to the embedding page, trapping wheel input inside the demo during scroll-driven mode.


    1-50: LGTM!


    1145-1282: LGTM!


    1615-1904: LGTM!

    apps/site/src/scripts/component-demo-runtime.ts (4)

    36-261: LGTM!


    463-549: LGTM!


    686-800: LGTM!


    802-882: LGTM!

    apps/site/src/components/ComponentDemo.astro (2)

    193-418: LGTM!


    423-436: LGTM!

    apps/site/public/feature-solver-en/touchai-components.html (3)

    426-430: Duplicate: preserve the mobile height override in scroll-driven completion.

    This still matches the previously flagged selector-specificity issue: body.is-scroll-driven.is-complete .chat-panel can override the mobile completion rule and restore the fixed desktop height on narrow viewports.


    1896-1916: Duplicate: this wheel handler still scrolls only the iframe.

    This still matches the previously flagged wheel-trapping issue: after preventDefault(), window.scrollBy() affects the iframe document, not the host page that owns the scroll-driven section.


    668-668: LGTM!

    Also applies to: 1875-1879, 2017-2017

    apps/site/public/feature-solver/touchai-components.html (3)

    426-430: Duplicate: preserve the mobile height override in scroll-driven completion.

    This still matches the previously flagged selector-specificity issue: the scroll-driven completion selector can win over the mobile body.is-complete .chat-panel rule and force the desktop 657px panel height.


    1986-2006: Duplicate: this wheel handler still scrolls only the iframe.

    This still matches the previously flagged wheel-trapping issue: preventDefault() swallows the wheel event, then window.scrollBy() runs inside the iframe instead of advancing the parent page.


    668-668: LGTM!

    Also applies to: 1607-1607, 1965-1969, 2103-2103

    apps/site/public/feature-work-organizer/touchai-components.html (21)

    1-6: LGTM!


    7-88: LGTM!


    89-171: LGTM!


    172-260: LGTM!


    261-376: LGTM!


    377-468: LGTM!


    469-516: LGTM!


    517-600: LGTM!


    601-700: LGTM!


    701-802: LGTM!


    803-952: LGTM!


    953-1064: LGTM!


    1065-1159: LGTM!


    1160-1241: LGTM!


    1242-1336: LGTM!


    1337-1420: LGTM!


    1421-1470: LGTM!


    1521-1606: LGTM!


    1607-1726: LGTM!


    1727-1795: LGTM!


    1471-1520: No action needed - demos are inlined, not embedded in iframes.

    The review comment assumes the demos run in iframe contexts, but the ComponentDemo.astro component inlines the demo HTML directly into the page using set:html. This means the demo JavaScript runs in the parent page's window context, making window.scrollBy() the correct approach to scroll the parent page. Using window.parent.scrollBy() would not work in this architecture and is unnecessary.

    			> Likely an incorrect or invalid review comment.
    

    Comment thread apps/site/scripts/check-homepage-regressions.mjs 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.

    Actionable comments posted: 1

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (16)
    apps/site/public/touchai-intro/touchai-components.html (2)

    501-505: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Scope the fixed scroll-driven complete height to desktop.

    Line 501 is more specific than the mobile complete rule, so short/mobile viewports can get forced back to 657px when scrub progress reaches the complete state. Wrap this rule in the same desktop media query used by the reminder demo.

    Suggested CSS fix
    -            body.is-scroll-driven.is-complete .chat-panel {
    -                min-height: var(--window-min-height);
    -                max-height: var(--window-min-height);
    -                height: var(--window-min-height);
    -            }
    +            `@media` (min-width: 841px) {
    +                body.is-scroll-driven.is-complete .chat-panel {
    +                    min-height: var(--window-min-height);
    +                    max-height: var(--window-min-height);
    +                    height: var(--window-min-height);
    +                }
    +            }

    Also applies to: 1065-1068

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/touchai-intro/touchai-components.html` around lines 501 -
    505, The CSS rule for body.is-scroll-driven.is-complete .chat-panel is too
    specific and overrides mobile height rules when scroll-driven progress reaches
    the complete state. Wrap this rule (which appears at lines 501-505 and also at
    lines 1065-1068) in a desktop media query, using the same media query pattern
    already used for the reminder demo in this file, to prevent the fixed height
    from being applied to mobile viewports.
    

    1201-1205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Forward wheel deltas to the parent instead of scrolling the iframe.

    After preventDefault(), window.scrollBy() only targets this iframe document, so scroll-driven mode traps wheel input instead of advancing the host page. Send the delta over the existing parent message channel and handle it in the host.

    Suggested child-side change
    -                function notifyParent(type) {
    +                function notifyParent(type, payload = {}) {
                         try {
                             if (window.parent && window.parent !== window) {
    -                            window.parent.postMessage({ type }, window.location.origin);
    +                            window.parent.postMessage({ type, ...payload }, window.location.origin);
                             }
                         } catch (error) {}
                     }
    ...
                         event.preventDefault();
    -                    window.scrollBy(0, nextDeltaY);
    +                    notifyParent('touchai-wheel-delta', { deltaY: nextDeltaY });
                     }

    Also applies to: 1560-1579

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/touchai-intro/touchai-components.html` around lines 1201 -
    1205, The iframe is currently preventing default wheel behavior and scrolling
    itself, which traps wheel input. Instead, modify the wheel event handler to
    capture the wheel delta values (deltaX and deltaY) from the wheel event, and
    send these delta values to the parent via the notifyParent function's
    postMessage channel. Remove the window.scrollBy() call from the iframe's wheel
    handler so the parent page can handle the scrolling instead of being trapped by
    the iframe's scroll behavior.
    
    apps/site/public/touchai-intro-en/touchai-components.html (1)

    1191-1195: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Forward wheel deltas to the host page.

    window.scrollBy() only scrolls the iframe after Line 1505 prevents the default wheel behavior, so embedded scroll-driven demos can swallow wheel input. Post the delta to the parent and let the host scroll handler consume it.

    Suggested child-side change
    -                function notifyParent(type) {
    +                function notifyParent(type, payload = {}) {
                         try {
                             if (window.parent && window.parent !== window) {
    -                            window.parent.postMessage({ type }, window.location.origin);
    +                            window.parent.postMessage({ type, ...payload }, window.location.origin);
                             }
                         } catch (error) {}
                     }
    ...
                         event.preventDefault();
    -                    window.scrollBy(0, nextDeltaY);
    +                    notifyParent('touchai-wheel-delta', { deltaY: nextDeltaY });
                     }

    Also applies to: 1487-1506

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/touchai-intro-en/touchai-components.html` around lines 1191
    - 1195, The notifyParent function currently only posts message types to the
    parent window, but does not forward wheel delta information that would allow the
    parent page's scroll handler to consume scroll events. Modify the notifyParent
    function to accept wheel delta values (deltaX, deltaY) as parameters along with
    the type, and include these values in the postMessage payload being sent to
    window.parent. Additionally, locate the wheel event handler (likely in the
    vicinity of lines 1487-1506) and ensure it calls notifyParent with the type and
    the wheel event's deltaX and deltaY values so the parent can properly handle
    scrolling for embedded scroll-driven demos.
    
    apps/site/public/feature-reminder/touchai-components.html (1)

    1595-1598: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Do not let keepLatestResponseVisible() override the scrub threshold.

    Line 1732 gates is-scrolling on scrubProgress >= 0.66, but Line 1597 immediately resets it to maxScroll > 1, causing the scroll layout to activate as soon as content overflows.

    Suggested fix
                     function keepLatestResponseVisible() {
                         const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -                    document.body.classList.toggle('is-scrolling', maxScroll > 1);
                         content.scrollTop = maxScroll;
    +                    return maxScroll;
                     }

    Also applies to: 1731-1736

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-reminder/touchai-components.html` around lines 1595
    - 1598, The keepLatestResponseVisible() function is unconditionally toggling the
    is-scrolling class based on maxScroll > 1, overriding the scrub threshold gate
    that requires scrubProgress >= 0.66. Modify the toggle condition in
    keepLatestResponseVisible() to respect both constraints: only apply the
    is-scrolling class when maxScroll > 1 AND scrubProgress >= 0.66 are both true,
    ensuring the scroll layout respects the scrub threshold requirement defined
    elsewhere in the code.
    
    apps/site/public/feature-solver/touchai-components.html (1)

    1967-1970: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Do not overwrite the scroll-driven threshold.

    Line 2101 gates is-scrolling on scrubProgress >= 0.66, but keepLatestResponseVisible() immediately toggles the class back on whenever the solver content overflows. The long solution will enter scrolling layout before the scrub threshold.

    Proposed fix
     function keepLatestResponseVisible() {
         const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -    document.body.classList.toggle('is-scrolling', maxScroll > 1);
         content.scrollTop = maxScroll;
     }

    Also applies to: 2100-2105

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-solver/touchai-components.html` around lines 1967 -
    1970, The keepLatestResponseVisible() function is unconditionally toggling the
    is-scrolling class whenever the solver content overflows, which overwrites the
    scroll-driven threshold check that should gate the class on scrubProgress >=
    0.66. Remove the document.body.classList.toggle() call from
    keepLatestResponseVisible() and only keep the content.scrollTop assignment,
    allowing the is-scrolling class to be controlled exclusively by the scrub
    progress threshold check elsewhere in the code.
    
    apps/site/public/feature-reminder-en/touchai-components.html (2)

    1622-1642: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Forward wheel deltas to the parent instead of scrolling the iframe.

    After Line 1640 calls preventDefault(), window.scrollBy() only targets this iframe document, so wheel input is swallowed during scroll-driven playback instead of continuing the host page scroll.

    Proposed fix
    -    event.preventDefault();
    -    window.scrollBy(0, nextDeltaY);
    +    try {
    +        if (window.parent && window.parent !== window) {
    +            event.preventDefault();
    +            window.parent.scrollBy(0, nextDeltaY);
    +        }
    +    } catch (error) {}
     }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-reminder-en/touchai-components.html` around lines
    1622 - 1642, The forwardWheelToPage function is calling preventDefault()
    followed by window.scrollBy(), which only scrolls the iframe's own document
    instead of forwarding the wheel input to the parent page. Replace the
    window.scrollBy(0, nextDeltaY) call with window.parent.scrollBy(0, nextDeltaY)
    so that the wheel delta is forwarded to and scrolls the host/parent page instead
    of being consumed by the iframe document.
    

    1601-1604: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Preserve the scrub threshold when pinning the response.

    Line 1738 sets is-scrolling only after scrubProgress >= 0.66, but keepLatestResponseVisible() immediately overwrites it based only on overflow. Once the response overflows, the scroll/composer state turns on before the intended scrub point.

    Proposed fix
     function keepLatestResponseVisible() {
         const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -    document.body.classList.toggle('is-scrolling', maxScroll > 1);
         content.scrollTop = maxScroll;
     }

    Also applies to: 1737-1742

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-reminder-en/touchai-components.html` around lines
    1601 - 1604, The keepLatestResponseVisible() function is unconditionally
    toggling the is-scrolling class based solely on whether content overflows
    (maxScroll > 1), which overwrites the scrub threshold logic defined elsewhere
    that should only enable is-scrolling when scrubProgress reaches 0.66 or higher.
    Modify the keepLatestResponseVisible() function to respect the scrub threshold
    constraint instead of immediately setting is-scrolling on any overflow; the
    class should only be toggled when both the overflow condition is met and the
    scrub progress threshold (0.66) has been reached, preserving the intended
    scroll/composer state behavior.
    
    apps/site/public/feature-solver-en/touchai-components.html (1)

    1877-1880: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Keep is-scrolling controlled by the scrub threshold.

    Line 2015 correctly gates is-scrolling on scrubProgress >= 0.66, but keepLatestResponseVisible() immediately re-enables it whenever the long answer overflows. That makes the scroll/composer layout switch too early.

    Proposed fix
     function keepLatestResponseVisible() {
         const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -    document.body.classList.toggle('is-scrolling', maxScroll > 1);
         content.scrollTop = maxScroll;
     }

    Also applies to: 2014-2019

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-solver-en/touchai-components.html` around lines 1877
    - 1880, The keepLatestResponseVisible() function is toggling the is-scrolling
    class based on content overflow, which conflicts with the scrub threshold
    control that correctly gates is-scrolling on scrubProgress >= 0.66 elsewhere.
    Remove the document.body.classList.toggle line that controls is-scrolling based
    on maxScroll overflow, keeping only the content.scrollTop assignment for scroll
    positioning. This ensures the is-scrolling class is exclusively controlled by
    the scrub threshold check, preventing premature layout switching.
    
    apps/site/public/feature-work-organizer-en/touchai-components.html (2)

    1495-1514: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Forward wheel scrolling to the host page, not the iframe.

    In scroll-driven mode this handler calls preventDefault(), then Line 1514 scrolls the iframe’s own window. For the embedded demo, that consumes the wheel event without advancing the landing page; scroll the parent window or post the delta to the host runtime instead.

    🐛 Proposed fix
     function forwardWheelToPage(event) {
    -    if (event.ctrlKey || event.metaKey) {
    -        event.preventDefault();
    -        return;
    -    }
    -
         if (!isScrollDriven || !hasReceivedScrollProgress) {
             return;
         }
    +
    +    if (event.ctrlKey || event.metaKey) {
    +        return;
    +    }
    
         const deltaMultiplier =
             event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? window.innerHeight : 1;
    @@
     
         event.preventDefault();
    -    window.scrollBy(0, nextDeltaY);
    +    const scrollWindow = window.parent && window.parent !== window ? window.parent : window;
    +    scrollWindow.scrollBy(0, nextDeltaY);
     }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html` around
    lines 1495 - 1514, The forwardWheelToPage function currently scrolls the
    iframe's own window using window.scrollBy, which prevents the wheel event from
    advancing the parent landing page. Instead of scrolling the iframe window with
    window.scrollBy(0, nextDeltaY), scroll the parent window using
    window.parent.scrollBy(0, nextDeltaY) to forward the wheel movement to the host
    page, ensuring the embedded demo properly delegates scrolling to the parent
    document.
    

    1474-1478: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Preserve the scroll threshold before pinning to the bottom.

    Line 1612 gates is-scrolling behind scrubProgress >= 0.66, but Line 1616 immediately calls keepLatestResponseVisible(), which toggles is-scrolling back on for any overflow. This bypasses the threshold and can make the panel jump into scrolling mode too early.

    🐛 Proposed fix
     function keepLatestResponseVisible() {
         const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -    document.body.classList.toggle('is-scrolling', maxScroll > 1);
         content.scrollTop = maxScroll;
     }
    
    @@
    -const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -document.body.classList.toggle(
    -    'is-scrolling',
    -    maxScroll > 1 && scrubProgress >= 0.66
    -);
    -keepLatestResponseVisible();
    +const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    +const shouldAutoScroll = maxScroll > 1 && scrubProgress >= 0.66;
    +document.body.classList.toggle('is-scrolling', shouldAutoScroll);
    +if (shouldAutoScroll) {
    +    keepLatestResponseVisible();
    +}
     updateScrollAffordance();

    Also applies to: 1611-1617

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html` around
    lines 1474 - 1478, The keepLatestResponseVisible() function toggles the
    is-scrolling class unconditionally whenever there is overflow, which bypasses
    the scroll threshold gating at scrubProgress >= 0.66. Modify
    keepLatestResponseVisible() to respect the threshold condition before toggling
    is-scrolling, either by passing the scrubProgress threshold as a parameter or
    checking it within the function, so that is-scrolling is only activated when the
    threshold is actually met and the panel does not jump into scrolling mode
    prematurely.
    
    apps/site/public/feature-work-organizer/touchai-components.html (2)

    1468-1472: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Preserve the scroll threshold before pinning to the bottom.

    Line 1602 gates is-scrolling behind scrubProgress >= 0.66, but Line 1606 immediately calls keepLatestResponseVisible(), which toggles is-scrolling back on for any overflow. This bypasses the threshold and can make the panel jump into scrolling mode too early.

    🐛 Proposed fix
     function keepLatestResponseVisible() {
         const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -    document.body.classList.toggle('is-scrolling', maxScroll > 1);
         content.scrollTop = maxScroll;
     }
    
    @@
    -const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    -document.body.classList.toggle(
    -    'is-scrolling',
    -    maxScroll > 1 && scrubProgress >= 0.66
    -);
    -keepLatestResponseVisible();
    +const maxScroll = Math.max(0, content.scrollHeight - content.clientHeight);
    +const shouldAutoScroll = maxScroll > 1 && scrubProgress >= 0.66;
    +document.body.classList.toggle('is-scrolling', shouldAutoScroll);
    +if (shouldAutoScroll) {
    +    keepLatestResponseVisible();
    +}
     updateScrollAffordance();

    Also applies to: 1601-1607

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer/touchai-components.html` around lines
    1468 - 1472, The keepLatestResponseVisible() function unconditionally toggles
    the is-scrolling class based on whether content overflows, which bypasses the
    scroll threshold check that was previously applied at line 1602. Instead of
    having keepLatestResponseVisible() toggle the is-scrolling class, modify it to
    only handle scrolling the content to the bottom by setting content.scrollTop to
    maxScroll, and remove the toggle logic that checks maxScroll > 1. Preserve the
    is-scrolling class state that was already determined by the threshold check at
    line 1602 so the panel does not jump into scrolling mode prematurely.
    

    1489-1508: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Forward wheel scrolling to the host page, not the iframe.

    In scroll-driven mode this handler calls preventDefault(), then Line 1508 scrolls the iframe’s own window. For the embedded demo, that consumes the wheel event without advancing the landing page; scroll the parent window or post the delta to the host runtime instead.

    🐛 Proposed fix
     function forwardWheelToPage(event) {
    -    if (event.ctrlKey || event.metaKey) {
    -        event.preventDefault();
    -        return;
    -    }
    -
         if (!isScrollDriven || !hasReceivedScrollProgress) {
             return;
         }
    +
    +    if (event.ctrlKey || event.metaKey) {
    +        return;
    +    }
    
         const deltaMultiplier =
             event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? window.innerHeight : 1;
    @@
     
         event.preventDefault();
    -    window.scrollBy(0, nextDeltaY);
    +    const scrollWindow = window.parent && window.parent !== window ? window.parent : window;
    +    scrollWindow.scrollBy(0, nextDeltaY);
     }
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/public/feature-work-organizer/touchai-components.html` around lines
    1489 - 1508, The forwardWheelToPage function currently scrolls the iframe's own
    window using window.scrollBy(0, nextDeltaY), which prevents the host page from
    scrolling. Replace this call with window.parent.scrollBy(0, nextDeltaY) to
    forward the wheel delta to the parent/host window instead, ensuring the landing
    page advances rather than being consumed within the embedded iframe.
    
    apps/site/src/pages/index.astro (4)

    336-343: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    Make the nav fade overlay non-interactive.

    .nav::after extends 24px below the sticky header and currently participates in hit testing, so the transparent strip can block clicks on content underneath. It should be visual-only.

    Proposed fix
     .nav::after {
         content: '';
         position: absolute;
         left: 0;
         right: 0;
         bottom: -24px;
         height: 24px;
    -    pointer-events: auto;
    +    pointer-events: none;
         opacity: 0;
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` around lines 336 - 343, The `.nav::after`
    pseudo-element currently has `pointer-events: auto` which causes it to
    participate in hit testing and blocks clicks on content underneath the 24px
    transparent overlay below the sticky header. Change the `pointer-events`
    property from `auto` to `none` on the `.nav::after` rule so the overlay becomes
    visual-only and does not interfere with interactions on the content below it.
    

    3485-3490: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

    Clear feature demos after leaving their scroll range.

    Feature triggers set terminal progress but never send touchai-clear-scroll-driven, unlike the intro trigger. Send the idle signal after 0/1 so feature iframes do not remain stuck in scroll-driven mode offscreen.

    Proposed fix
     onLeave: () => {
         syncFeatureDemoProgress(frame, 1);
    +    queueDemoScrollIdle(frame);
     },
     onLeaveBack: () => {
         syncFeatureDemoProgress(frame, 0);
    +    queueDemoScrollIdle(frame);
     },
    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` around lines 3485 - 3490, The feature demo
    scroll triggers in the onLeave and onLeaveBack callbacks are missing the idle
    signal that clears scroll-driven mode from the iframes. After each call to
    syncFeatureDemoProgress in both the onLeave and onLeaveBack callbacks, send the
    touchai-clear-scroll-driven signal to the frame to ensure feature iframes
    properly exit scroll-driven mode when they leave the viewport, preventing them
    from remaining stuck offscreen.
    

    2042-2059: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Re-add the open-issues stat using the issues search API.

    The current stats model and UI only render Stars/Forks, so the PR’s “Open issues” correction is no longer represented. Add an issues stat wired to search/issues?q=repo:${repoPath} is:issue state:open so pull requests stay excluded.

    Also applies to: 2802-2899

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` around lines 2042 - 2059, Add a third
    github-stat div block for open issues to the github-stats container, following
    the same structure as the existing stars and forks stats. The new issues stat
    should use a strong element with data-github-stat="issues" attribute, a
    corresponding span with data-i18n="teams.stats.issues" label, and ensure the
    parent github-stats div includes data-initial-issues attribute with the initial
    open issues count. The issues data should be configured to use the GitHub search
    API endpoint with the query format search/issues?q=repo:${repoPath} is:issue
    state:open to properly exclude pull requests from the count.
    

    2456-2505: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

    Reset progress dedupe when demos are cleared or reloaded.

    lastDispatchedDemoProgress survives reload() and touchai-clear-scroll-driven. If the intro iframe reloads during language switching and the current progress is unchanged, queueDemoProgress drops the ready-time sync, leaving the new iframe unsynced until scroll changes.

    Proposed direction
    +const resetDemoProgressDedupe = (frame) => {
    +    if (!frame) return;
    +    lastDispatchedDemoProgress.delete(frame);
    +};
    
     const cancelQueuedDemoProgress = (frame) => {
         if (!frame) return;
         const frameHandle = demoProgressFrameHandles.get(frame);
         if (frameHandle) {
             cancelAnimationFrame(frameHandle);
             demoProgressFrameHandles.delete(frame);
         }
         pendingDemoProgress.delete(frame);
    +    resetDemoProgressDedupe(frame);
     };
    
     const queueDemoScrollIdle = (frame) => {
         if (!frame) return;
         requestAnimationFrame(() => {
    +        resetDemoProgressDedupe(frame);
             sendToDemo(frame, { type: 'touchai-clear-scroll-driven' });
         });
     };

    Also call resetDemoProgressDedupe(introDemo) before changing introDemo.dataset.src, or force the intro ready-time progress sync.

    Also applies to: 2568-2576, 3348-3354

    🤖 Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@apps/site/src/pages/index.astro` around lines 2456 - 2505, The
    `lastDispatchedDemoProgress` Map persists across demo reloads and clear
    operations, causing the deduplication logic in `queueDemoProgress` to skip
    progress updates when the value hasn't changed since the last dispatch. This
    leaves newly reloaded iframes unsynced. Create a function called
    `resetDemoProgressDedupe` that clears entries from the
    `lastDispatchedDemoProgress` Map for a given frame, then call this function
    before changing `introDemo.dataset.src` or when handling the
    touchai-clear-scroll-driven message in `queueDemoScrollIdle` to ensure the
    deduplication state is reset and progress is re-synced to reloaded demos.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@apps/site/src/pages/index.astro`:
    - Line 1803: The ARIA labels for accessibility are hardcoded in Chinese and do
    not respond to language changes, while visible copy properly localizes. Update
    the nav header aria-label attribute (currently showing "主导航"), replay-card
    labels, and theme toggle label/title to use the same locale-aware pattern used
    for visible copy that switches between languages. Additionally, review the
    execution order of setTheme() and setLanguage() functions to ensure that
    setTheme() does not run after setLanguage() and overwrite the theme toggle text
    with Chinese. Apply this locale-aware pattern consistently across all identified
    locations including the nav aria-label, replay-card ARIA labels, and theme
    toggle related labels.
    
    ---
    
    Outside diff comments:
    In `@apps/site/public/feature-reminder-en/touchai-components.html`:
    - Around line 1622-1642: The forwardWheelToPage function is calling
    preventDefault() followed by window.scrollBy(), which only scrolls the iframe's
    own document instead of forwarding the wheel input to the parent page. Replace
    the window.scrollBy(0, nextDeltaY) call with window.parent.scrollBy(0,
    nextDeltaY) so that the wheel delta is forwarded to and scrolls the host/parent
    page instead of being consumed by the iframe document.
    - Around line 1601-1604: The keepLatestResponseVisible() function is
    unconditionally toggling the is-scrolling class based solely on whether content
    overflows (maxScroll > 1), which overwrites the scrub threshold logic defined
    elsewhere that should only enable is-scrolling when scrubProgress reaches 0.66
    or higher. Modify the keepLatestResponseVisible() function to respect the scrub
    threshold constraint instead of immediately setting is-scrolling on any
    overflow; the class should only be toggled when both the overflow condition is
    met and the scrub progress threshold (0.66) has been reached, preserving the
    intended scroll/composer state behavior.
    
    In `@apps/site/public/feature-reminder/touchai-components.html`:
    - Around line 1595-1598: The keepLatestResponseVisible() function is
    unconditionally toggling the is-scrolling class based on maxScroll > 1,
    overriding the scrub threshold gate that requires scrubProgress >= 0.66. Modify
    the toggle condition in keepLatestResponseVisible() to respect both constraints:
    only apply the is-scrolling class when maxScroll > 1 AND scrubProgress >= 0.66
    are both true, ensuring the scroll layout respects the scrub threshold
    requirement defined elsewhere in the code.
    
    In `@apps/site/public/feature-solver-en/touchai-components.html`:
    - Around line 1877-1880: The keepLatestResponseVisible() function is toggling
    the is-scrolling class based on content overflow, which conflicts with the scrub
    threshold control that correctly gates is-scrolling on scrubProgress >= 0.66
    elsewhere. Remove the document.body.classList.toggle line that controls
    is-scrolling based on maxScroll overflow, keeping only the content.scrollTop
    assignment for scroll positioning. This ensures the is-scrolling class is
    exclusively controlled by the scrub threshold check, preventing premature layout
    switching.
    
    In `@apps/site/public/feature-solver/touchai-components.html`:
    - Around line 1967-1970: The keepLatestResponseVisible() function is
    unconditionally toggling the is-scrolling class whenever the solver content
    overflows, which overwrites the scroll-driven threshold check that should gate
    the class on scrubProgress >= 0.66. Remove the document.body.classList.toggle()
    call from keepLatestResponseVisible() and only keep the content.scrollTop
    assignment, allowing the is-scrolling class to be controlled exclusively by the
    scrub progress threshold check elsewhere in the code.
    
    In `@apps/site/public/feature-work-organizer-en/touchai-components.html`:
    - Around line 1495-1514: The forwardWheelToPage function currently scrolls the
    iframe's own window using window.scrollBy, which prevents the wheel event from
    advancing the parent landing page. Instead of scrolling the iframe window with
    window.scrollBy(0, nextDeltaY), scroll the parent window using
    window.parent.scrollBy(0, nextDeltaY) to forward the wheel movement to the host
    page, ensuring the embedded demo properly delegates scrolling to the parent
    document.
    - Around line 1474-1478: The keepLatestResponseVisible() function toggles the
    is-scrolling class unconditionally whenever there is overflow, which bypasses
    the scroll threshold gating at scrubProgress >= 0.66. Modify
    keepLatestResponseVisible() to respect the threshold condition before toggling
    is-scrolling, either by passing the scrubProgress threshold as a parameter or
    checking it within the function, so that is-scrolling is only activated when the
    threshold is actually met and the panel does not jump into scrolling mode
    prematurely.
    
    In `@apps/site/public/feature-work-organizer/touchai-components.html`:
    - Around line 1468-1472: The keepLatestResponseVisible() function
    unconditionally toggles the is-scrolling class based on whether content
    overflows, which bypasses the scroll threshold check that was previously applied
    at line 1602. Instead of having keepLatestResponseVisible() toggle the
    is-scrolling class, modify it to only handle scrolling the content to the bottom
    by setting content.scrollTop to maxScroll, and remove the toggle logic that
    checks maxScroll > 1. Preserve the is-scrolling class state that was already
    determined by the threshold check at line 1602 so the panel does not jump into
    scrolling mode prematurely.
    - Around line 1489-1508: The forwardWheelToPage function currently scrolls the
    iframe's own window using window.scrollBy(0, nextDeltaY), which prevents the
    host page from scrolling. Replace this call with window.parent.scrollBy(0,
    nextDeltaY) to forward the wheel delta to the parent/host window instead,
    ensuring the landing page advances rather than being consumed within the
    embedded iframe.
    
    In `@apps/site/public/touchai-intro-en/touchai-components.html`:
    - Around line 1191-1195: The notifyParent function currently only posts message
    types to the parent window, but does not forward wheel delta information that
    would allow the parent page's scroll handler to consume scroll events. Modify
    the notifyParent function to accept wheel delta values (deltaX, deltaY) as
    parameters along with the type, and include these values in the postMessage
    payload being sent to window.parent. Additionally, locate the wheel event
    handler (likely in the vicinity of lines 1487-1506) and ensure it calls
    notifyParent with the type and the wheel event's deltaX and deltaY values so the
    parent can properly handle scrolling for embedded scroll-driven demos.
    
    In `@apps/site/public/touchai-intro/touchai-components.html`:
    - Around line 501-505: The CSS rule for body.is-scroll-driven.is-complete
    .chat-panel is too specific and overrides mobile height rules when scroll-driven
    progress reaches the complete state. Wrap this rule (which appears at lines
    501-505 and also at lines 1065-1068) in a desktop media query, using the same
    media query pattern already used for the reminder demo in this file, to prevent
    the fixed height from being applied to mobile viewports.
    - Around line 1201-1205: The iframe is currently preventing default wheel
    behavior and scrolling itself, which traps wheel input. Instead, modify the
    wheel event handler to capture the wheel delta values (deltaX and deltaY) from
    the wheel event, and send these delta values to the parent via the notifyParent
    function's postMessage channel. Remove the window.scrollBy() call from the
    iframe's wheel handler so the parent page can handle the scrolling instead of
    being trapped by the iframe's scroll behavior.
    
    In `@apps/site/src/pages/index.astro`:
    - Around line 336-343: The `.nav::after` pseudo-element currently has
    `pointer-events: auto` which causes it to participate in hit testing and blocks
    clicks on content underneath the 24px transparent overlay below the sticky
    header. Change the `pointer-events` property from `auto` to `none` on the
    `.nav::after` rule so the overlay becomes visual-only and does not interfere
    with interactions on the content below it.
    - Around line 3485-3490: The feature demo scroll triggers in the onLeave and
    onLeaveBack callbacks are missing the idle signal that clears scroll-driven mode
    from the iframes. After each call to syncFeatureDemoProgress in both the onLeave
    and onLeaveBack callbacks, send the touchai-clear-scroll-driven signal to the
    frame to ensure feature iframes properly exit scroll-driven mode when they leave
    the viewport, preventing them from remaining stuck offscreen.
    - Around line 2042-2059: Add a third github-stat div block for open issues to
    the github-stats container, following the same structure as the existing stars
    and forks stats. The new issues stat should use a strong element with
    data-github-stat="issues" attribute, a corresponding span with
    data-i18n="teams.stats.issues" label, and ensure the parent github-stats div
    includes data-initial-issues attribute with the initial open issues count. The
    issues data should be configured to use the GitHub search API endpoint with the
    query format search/issues?q=repo:${repoPath} is:issue state:open to properly
    exclude pull requests from the count.
    - Around line 2456-2505: The `lastDispatchedDemoProgress` Map persists across
    demo reloads and clear operations, causing the deduplication logic in
    `queueDemoProgress` to skip progress updates when the value hasn't changed since
    the last dispatch. This leaves newly reloaded iframes unsynced. Create a
    function called `resetDemoProgressDedupe` that clears entries from the
    `lastDispatchedDemoProgress` Map for a given frame, then call this function
    before changing `introDemo.dataset.src` or when handling the
    touchai-clear-scroll-driven message in `queueDemoScrollIdle` to ensure the
    deduplication state is reset and progress is re-synced to reloaded demos.
    
    🪄 Autofix (Beta)

    Fix all unresolved CodeRabbit comments on this PR:

    • Push a commit to this branch (recommended)
    • Create a new PR with the fixes

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: 69b89b87-6ed7-45a1-8ffa-e3983d1ae62e

    📥 Commits

    Reviewing files that changed from the base of the PR and between 7744256 and 3a7a4d1.

    ⛔ Files ignored due to path filters (1)
    • apps/site/public/seo-card.png is excluded by !**/*.png
    📒 Files selected for processing (10)
    • apps/site/.env.example
    • apps/site/public/feature-reminder-en/touchai-components.html
    • apps/site/public/feature-reminder/touchai-components.html
    • apps/site/public/feature-solver-en/touchai-components.html
    • apps/site/public/feature-solver/touchai-components.html
    • apps/site/public/feature-work-organizer-en/touchai-components.html
    • apps/site/public/feature-work-organizer/touchai-components.html
    • apps/site/public/touchai-intro-en/touchai-components.html
    • apps/site/public/touchai-intro/touchai-components.html
    • apps/site/src/pages/index.astro
    💤 Files with no reviewable changes (1)
    • apps/site/.env.example
    📜 Review details
    ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
    • GitHub Check: CodeQL (rust)
    🧰 Additional context used
    🪛 ast-grep (0.43.0)
    apps/site/public/feature-reminder-en/touchai-components.html

    [warning] 1665-1665: Avoid using the initial state variable in setState
    Context: setToolCallVisibility(ratio)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (setstate-same-var)

    apps/site/public/feature-reminder/touchai-components.html

    [warning] 1659-1659: Avoid using the initial state variable in setState
    Context: setToolCallVisibility(ratio)
    Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

    (setstate-same-var)

    🔇 Additional comments (8)
    apps/site/public/feature-reminder/touchai-components.html (2)

    1616-1636: Duplicate: wheel deltas are still swallowed inside the iframe.

    This is the same unresolved issue already flagged on this file: after preventDefault(), window.scrollBy() only scrolls the iframe document, not the embedding page.


    511-517: LGTM!

    apps/site/public/feature-solver/touchai-components.html (1)

    1988-2008: Duplicate: wheel input is still trapped inside the iframe.

    This is the same issue previously flagged: Line 2006 prevents the native wheel behavior, then Line 2007 scrolls only the iframe window rather than the host page.

    apps/site/public/feature-solver-en/touchai-components.html (1)

    1898-1918: Duplicate: wheel input is still trapped inside the iframe.

    This is the same issue previously flagged: Line 1916 prevents default wheel behavior, then Line 1917 scrolls only the iframe window instead of the host page.

    apps/site/public/feature-work-organizer/touchai-components.html (1)

    511-517: LGTM!

    Also applies to: 1118-1123, 1137-1144, 1162-1162, 1730-1767

    apps/site/public/feature-work-organizer-en/touchai-components.html (1)

    2-2: LGTM!

    Also applies to: 1070-1159, 1168-1168, 1235-1244

    apps/site/src/pages/index.astro (2)

    1802-1802: Already covered: validate PUBLIC_ANALYTICS_ENDPOINT before emitting it.

    A previous review comment already requested build/client validation for this dataset value.


    627-634: LGTM!

    Comment thread apps/site/src/pages/index.astro
    coderabbitai[bot]
    coderabbitai Bot previously approved these changes Jun 17, 2026
    hiqiancheng
    hiqiancheng previously approved these changes Jun 17, 2026
    @hiqiancheng hiqiancheng added this pull request to the merge queue Jun 17, 2026
    @github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Jun 17, 2026
    @hiqiancheng hiqiancheng force-pushed the codex/site-scroll-demo-fixes branch from 4309fcc to 950efd4 Compare June 17, 2026 20:11
    @hiqiancheng hiqiancheng added this pull request to the merge queue Jun 17, 2026
    Merged via the queue into TouchAI-org:main with commit 8207a25 Jun 17, 2026
    27 checks passed
    @github-actions

    Copy link
    Copy Markdown
    Contributor

    Merged. Thank you for contributing to TouchAI again.

    已合并。感谢你再次为 TouchAI 做出贡献。

    We appreciate the continued help.
    感谢你持续参与项目建设。

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    area:frontend Frontend UI or view-layer changes

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants