fix(site): refresh landing demos and mobile scroll trigger#499
fix(site): refresh landing demos and mobile scroll trigger#499TheEverests wants to merge 3 commits into
Conversation
Assisted-by: OpenAI Codex
Assisted-by: OpenAI Codex
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR introduces two shared client-side utilities ( Scroll-Driven Demo Infrastructure, Analytics, and Site Config
Sequence Diagram(s)sequenceDiagram
participant Page as index.astro (ScrollTrigger)
participant Host as component-demo-runtime
participant Demo as touchai-components.html
Page->>Host: send({ type: "touchai-set-scroll-progress", progress })
Host->>Host: dispatchMessage — set is-scroll-driven on host dataset
Host->>Demo: postMessage(touchai-set-scroll-progress, progress)
Demo->>Demo: renderScrollProgress — scrub prompt, toggle aria-busy
Demo->>Demo: forwardWheelToPage (wheel → window.scrollBy)
Demo-->>Host: postMessage(touchai-*-ready)
Host-->>Page: touchai message event
Page->>Host: send({ type: "touchai-clear-scroll-driven" })
Host->>Demo: postMessage(touchai-clear-scroll-driven)
Demo->>Demo: setScrollDrivenState(false) — remove is-scroll-driven class
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error, 1 warning)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
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/pages/index.astro (1)
614-625: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winDo not let the retained Glimm canvas intercept the page.
ensureGlimmController()appends.glimm-click-layeronce, and completion only removesis-glimm-transitioning; withpointer-events: autoandz-index: 9999, the retained canvas can block all subsequent clicks.Proposed fix
.glimm-click-layer { position: fixed; inset: 0; z-index: 9999; width: 100vw; height: 100vh; - pointer-events: auto; + pointer-events: none; }Also applies to: 3053-3056, 3111-3114
🤖 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 614 - 625, The retained .glimm-click-layer is still intercepting the page because ensureGlimmController() leaves it mounted after the transition and the CSS keeps pointer-events enabled at z-index 9999. Update the transition cleanup so the Glimm layer is removed or made non-interactive once the animation completes, and keep body.is-glimm-transitioning .interactive-click as the only temporary pointer-events override. Use the .glimm-click-layer and ensureGlimmController() hooks to locate the cleanup path.
🤖 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/.env.example`:
- Around line 15-17: The analytics endpoint value is emitted into the page by
index.astro, so the .env.example entry should explicitly warn that
PUBLIC_ANALYTICS_ENDPOINT is client-visible and must not contain secrets or
tokens. Update the comment near PUBLIC_ANALYTICS_ENDPOINT to call out that
anything embedded in the URL will be exposed to visitors, and keep the guidance
aligned with how index.astro consumes this setting.
In `@apps/site/astro.config.mjs`:
- Line 13: The site config is disabling the only available 404 route, since
`apps/site/src/content/docs/404.md` is just the Starlight-injected 404 and there
is no real `src/pages/404.*` page. Update `astro.config.mjs` by removing
`disable404Route` from the Starlight config, or alternatively add an actual
Astro 404 page if you want to keep the flag; use the `disable404Route` setting
and the existing `404.md` customization as the key places to fix.
In `@apps/site/public/demo-utils/touchai-lite-math.js`:
- Line 29: The KaTeX fallback path has an empty catch block with an unused error
binding, which triggers the lint warning. Update the catch in the lightweight
renderer fallback so it uses a binding-free catch form, and keep the fallback
behavior in place within the same KaTeX rendering logic.
- Around line 4-6: The escapeHtml helper currently falls back to raw
String(value) when renderer is unavailable, which bypasses HTML escaping in
touchai-lite-math.js. Update escapeHtml so the no-renderer path still safely
escapes the value instead of returning unescaped text, and keep the
renderer.escapeHtml branch unchanged; use the escapeHtml function in
touchai-lite-math.js as the target for the fix.
In `@apps/site/public/demo-utils/touchai-lite-renderer.js`:
- Around line 26-29: Prettier is flagging the wrapped replace expressions in the
renderer, so update the formatting in touchai-lite-renderer’s markdown handling
to match the project’s style without changing behavior. Reflow the chained
escapeHtml(...).replace(...) call and the similar wrapped expression later in
the file so they satisfy Prettier, keeping the same regex and replacement logic.
In `@apps/site/public/feature-reminder-en/touchai-components.html`:
- Around line 1577-1580: The wheel bridge in forwardWheelToPage is suppressing
browser zoom gestures by calling preventDefault on Ctrl/Meta+wheel before any
forwarding logic runs. Update forwardWheelToPage so it returns immediately for
event.ctrlKey or event.metaKey without preventing default, and keep
preventDefault only on normal wheel events that are actually forwarded to the
page.
In `@apps/site/public/feature-reminder/touchai-components.html`:
- Around line 1823-1854: The message handler in touchai-components.html is
accepting control actions from any sender, so add a same-origin parent check
before processing touchai-reminder-start, touchai-clear-scroll-driven,
touchai-reset-demo, and touchai-set-scroll-progress. Update the
window.addEventListener('message', ...) flow to verify the event source/origin
with the existing getMessagePayload-based logic, and only apply these control
paths when the message comes from the parent on the same origin; leave the
touchai-page-bg path unchanged if it is meant to remain allowed.
In `@apps/site/public/feature-solver-en/touchai-components.html`:
- Line 2: The page is marked as English via the html lang attribute, but some
remaining ARIA labels/alt text are still Chinese, so update the affected
accessibility text in the touchai-components demo to English or explicitly scope
any Chinese-only strings with lang="zh-CN". Use the html element and the
relevant labeled UI elements in touchai-components.html to locate and fix the
remaining non-English labels.
- Around line 1968-1971: The wheel bridge in forwardWheelToPage is blocking
browser zoom gestures by calling preventDefault() for Ctrl/Meta+wheel before any
scroll forwarding logic runs. Update forwardWheelToPage so it returns
immediately for Ctrl/Meta-modified wheel events without preventing default, and
only call preventDefault() when handling a normal wheel delta that is actually
being forwarded to the page.
In `@apps/site/public/feature-solver/touchai-components.html`:
- Around line 1968-1971: In forwardWheelToPage, the early Ctrl/Meta+wheel branch
is suppressing browser zoom gestures by calling preventDefault before the
scroll-bridging logic runs. Update the wheel bridge so the modifier-key case
returns immediately without preventing default, and only call preventDefault for
normal wheel events that are actually forwarded to the page. Keep the fix
localized to forwardWheelToPage and its wheel forwarding/gating path.
In `@apps/site/public/feature-work-organizer-en/touchai-components.html`:
- Around line 1471-1474: The wheel bridge in forwardWheelToPage is swallowing
browser zoom gestures by calling preventDefault on Ctrl/Meta+wheel before any
scroll forwarding logic runs. Update forwardWheelToPage so it returns
immediately for modifier-based zoom gestures without preventing default, and
reserve preventDefault only for normal wheel events that are actually forwarded
to the page.
In `@apps/site/public/feature-work-organizer/touchai-components.html`:
- Around line 1469-1472: The wheel bridge in forwardWheelToPage is suppressing
browser zoom gestures by calling preventDefault for Ctrl/Meta+wheel before any
forwarding logic runs. Update forwardWheelToPage so it returns immediately for
event.ctrlKey or event.metaKey without preventing default, and only call
preventDefault when handling a normal wheel delta that should be forwarded to
the page. Use the existing forwardWheelToPage function and its wheel-gating
logic as the place to make this change.
In `@apps/site/public/touchai-intro-en/touchai-components.html`:
- Around line 1729-1755: The window message handler currently applies
`touchai-page-bg`, `touchai-clear-scroll-driven`, `touchai-reset-demo`, and
`touchai-set-progress` from any origin; update the
`window.addEventListener('message', ...)` logic to verify the sender matches the
expected same-origin parent before mutating state. Use the existing
`getMessagePayload` flow as the entry point, add an origin/source check before
handling any TouchAI control message, and keep the state updates
(`resetDemoState`, `setScrollDrivenState`, `scheduleScrollProgress`) gated
behind that validation.
In `@apps/site/public/touchai-intro/touchai-components.html`:
- Around line 1798-1824: The `window.addEventListener('message', ...)` handler
in the TouchAI demo accepts control messages from any sender, so restrict it
before calling `getMessagePayload(event)` or applying `touchai-page-bg`,
`touchai-clear-scroll-driven`, `touchai-reset-demo`, or `touchai-set-progress`.
Add an origin/source check at the start of the message handler to allow only the
expected parent window and same-origin messages, then ignore everything else.
Keep the validation close to the existing `message` handler logic so the
controls in `touchai-components.html` are only processed from trusted
`postMessage` senders.
In `@apps/site/src/pages/index.astro`:
- Around line 1889-1891: The hidden heading uses data-i18n="hero.heading" in the
hero section, but the translation key is missing from both language
dictionaries. Add hero.heading to the existing i18n dictionaries used by the
page (the same structures that define the other hero strings in index.astro) so
the screen-reader <h1> resolves correctly in both languages instead of falling
back to Chinese.
- Around line 2515-2519: The demo cleanup path is sending
touchai-clear-scroll-driven, but the host runtime only clears the scroll-driven
state through its reset contract for *-start and touchai-reset-demo. Update the
cleanup flow in queueDemoScrollIdle and the related sendToDemo callers so the
host receives the same reset signal it already understands, and make the
matching host-side handler clear is-scroll-driven/data state there instead of
relying on the unsupported clear event.
- Around line 2718-2749: The analytics payload built in sendAnalytics currently
includes full window.location.href and document.referrer values, which may
expose query strings, fragments, or sensitive paths. Update the payload
construction to use minimized location/referrer data instead of the full URL
fields, keeping the rest of the sendAnalytics logic unchanged and preserving the
same event metadata shape where possible.
In `@apps/site/src/scripts/component-demo-runtime.ts`:
- Around line 880-883: The lazy-load guard in startLoad currently leaves
host.dataset.touchaiLoadStarted set to true even when load() rejects, which
blocks later host.load?.() retries. Update the startLoad flow in
component-demo-runtime so it either tracks the in-flight promise and reuses it,
or resets touchaiLoadStarted in the failure path before rethrowing, ensuring the
lazy demo can retry after a transient load error.
- Around line 490-498: The pending-message dedupe logic in
component-demo-runtime’s message queue is replacing an older item in place,
which can change the final flush order. Update the branch that uses
getMessageType, pendingMessages.findIndex, and pendingMessages[existingIndex] so
that when a matching messageType is found, the old entry is removed and the new
data is appended to the end of pendingMessages instead of overwriting in place.
Keep the existing push behavior for new types so the queue preserves latest
chronology.
---
Outside diff comments:
In `@apps/site/src/pages/index.astro`:
- Around line 614-625: The retained .glimm-click-layer is still intercepting the
page because ensureGlimmController() leaves it mounted after the transition and
the CSS keeps pointer-events enabled at z-index 9999. Update the transition
cleanup so the Glimm layer is removed or made non-interactive once the animation
completes, and keep body.is-glimm-transitioning .interactive-click as the only
temporary pointer-events override. Use the .glimm-click-layer and
ensureGlimmController() hooks to locate the cleanup path.
🪄 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: 91719302-df14-43ba-a521-aaf35046f372
⛔ Files ignored due to path filters (5)
apps/desktop/src-tauri/Cargo.lockis excluded by!**/*.lockapps/site/public/apple-touch-icon.pngis excluded by!**/*.pngapps/site/public/icon-192.pngis excluded by!**/*.pngapps/site/public/icon-512.pngis excluded by!**/*.pngapps/site/public/seo-card.pngis excluded by!**/*.png
📒 Files selected for processing (20)
apps/site/.env.exampleapps/site/astro.config.mjsapps/site/public/demo-utils/touchai-lite-math.jsapps/site/public/demo-utils/touchai-lite-renderer.jsapps/site/public/feature-reminder-en/touchai-components.htmlapps/site/public/feature-reminder/touchai-components.htmlapps/site/public/feature-solver-en/touchai-components.htmlapps/site/public/feature-solver/touchai-components.htmlapps/site/public/feature-work-organizer-en/touchai-components.htmlapps/site/public/feature-work-organizer/touchai-components.htmlapps/site/public/robots.txtapps/site/public/site.webmanifestapps/site/public/touchai-intro-en/touchai-components.htmlapps/site/public/touchai-intro/touchai-components.htmlapps/site/src/components/ComponentDemo.astroapps/site/src/content.config.tsapps/site/src/content/docs/404.mdapps/site/src/content/docs/getting-started.mdapps/site/src/pages/index.astroapps/site/src/scripts/component-demo-runtime.ts
📜 Review details
🧰 Additional context used
🪛 ast-grep (0.44.0)
apps/site/public/demo-utils/touchai-lite-renderer.js
[warning] 2-6: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(manual-sanitization)
[warning] 2-5: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(manual-sanitization)
[warning] 2-4: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(manual-sanitization)
[warning] 2-3: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: String(value)
.replace(/&/g, '&')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(manual-sanitization)
apps/site/public/demo-utils/touchai-lite-math.js
[warning] 239-239: Detects non-literal values in regular expressions
Context: new RegExp(<p class="paragraph-node">${token}</p>, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity (ReDoS via non-literal RegExp).
(detect-non-literal-regexp)
[warning] 242-242: Detects non-literal values in regular expressions
Context: new RegExp(token, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity (ReDoS via non-literal RegExp).
(detect-non-literal-regexp)
apps/site/public/touchai-intro/touchai-components.html
[warning] 1631-1631: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 1797-1824: Check origin of events
Context: window.addEventListener('message', (event) => {
const message = getMessagePayload(event);
if (message.type === 'touchai-page-bg') {
document.documentElement.style.setProperty(
'--page-bg',
typeof message.background === 'string' ? message.background : 'transparent'
);
return;
}
if (message.type === 'touchai-clear-scroll-driven') {
hasReceivedScrollProgress = false;
setScrollDrivenState(false);
updateScrollAffordance();
return;
}
if (message.type === 'touchai-reset-demo') {
resetDemoState(false);
return;
}
if (message.type !== 'touchai-set-progress') {
return;
}
scheduleScrollProgress(message.progress);
})
Note: [CWE-346] Origin Validation Error (postMessage handler without origin check).
(event-check-origin)
apps/site/public/feature-solver-en/touchai-components.html
[warning] 2035-2035: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
apps/site/public/touchai-intro-en/touchai-components.html
[warning] 1560-1560: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 1728-1755: Check origin of events
Context: window.addEventListener('message', (event) => {
const message = getMessagePayload(event);
if (message.type === 'touchai-page-bg') {
document.documentElement.style.setProperty(
'--page-bg',
typeof message.background === 'string' ? message.background : 'transparent'
);
return;
}
if (message.type === 'touchai-clear-scroll-driven') {
hasReceivedScrollProgress = false;
setScrollDrivenState(false);
updateScrollAffordance();
return;
}
if (message.type === 'touchai-reset-demo') {
resetDemoState(false);
return;
}
if (message.type !== 'touchai-set-progress') {
return;
}
scheduleScrollProgress(message.progress);
})
Note: [CWE-346] Origin Validation Error (postMessage handler without origin check).
(event-check-origin)
apps/site/public/feature-reminder/touchai-components.html
[warning] 1638-1638: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
apps/site/public/feature-solver/touchai-components.html
[warning] 2031-2031: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
apps/site/public/feature-reminder-en/touchai-components.html
[warning] 1644-1644: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
apps/site/public/feature-work-organizer/touchai-components.html
[warning] 1532-1532: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
apps/site/public/feature-work-organizer-en/touchai-components.html
[warning] 1538-1538: Avoid using the initial state variable in setState
Context: setScrollDrivenState(hasReceivedScrollProgress)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🪛 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)·=>·
)⏎···················· with .map((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)
🔇 Additional comments (11)
apps/site/src/content.config.ts (1)
1-10: LGTM!apps/site/src/content/docs/getting-started.md (1)
1-29: LGTM!apps/site/public/site.webmanifest (1)
11-27: 🎯 Functional CorrectnessManifest icons are present. The referenced
/icon-192.pngand/icon-512.pngfiles exist underapps/site/public/.apps/site/src/pages/index.astro (1)
172-190: 📐 Maintainability & Code QualityKeep
is:inlineon these scripts. They’re intentional raw inline payloads, so removing the opt-out would change Astro’s script handling.> Likely an incorrect or invalid review comment.apps/site/src/components/ComponentDemo.astro (1)
13-13: LGTM!Also applies to: 173-182, 208-219, 262-271, 283-348, 366-426, 439-441
apps/site/public/feature-reminder-en/touchai-components.html (1)
86-89: LGTM!Also applies to: 155-168, 470-516, 760-760, 1197-1197, 1211-1211, 1229-1548, 1569-1576, 1581-1882
apps/site/public/feature-solver/touchai-components.html (1)
88-91: LGTM!Also applies to: 157-170, 400-446, 1048-1048, 1591-1652, 1739-1939, 1960-1967, 1972-2257
apps/site/public/feature-solver-en/touchai-components.html (1)
87-90: LGTM!Also applies to: 156-169, 400-446, 1048-1048, 1591-1652, 1739-1939, 1960-1967, 1972-2260
apps/site/public/feature-work-organizer-en/touchai-components.html (1)
86-89: LGTM!Also applies to: 155-168, 470-516, 760-760, 1118-1231, 1304-1440, 1463-1470, 1475-1774
apps/site/public/feature-work-organizer/touchai-components.html (1)
86-89: LGTM!Also applies to: 155-168, 470-516, 760-760, 1116-1229, 1302-1438, 1461-1468, 1473-1769
apps/site/src/scripts/component-demo-runtime.ts (1)
11-13: LGTM!Also applies to: 49-263, 466-489, 499-549, 689-715, 734-800, 820-822, 887-946
| # Generic analytics collector endpoint that accepts JSON POST payloads. | ||
| # Leave empty to disable the built-in pageview and click tracking. | ||
| PUBLIC_ANALYTICS_ENDPOINT= |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
Warn that the analytics endpoint is client-visible.
index.astro emits this value into the page, so tokens embedded in the URL would be exposed to every visitor.
Proposed doc tweak
# Generic analytics collector endpoint that accepts JSON POST payloads.
+# This value is exposed to browsers; do not include secrets or private write tokens.
# Leave empty to disable the built-in pageview and click tracking.
PUBLIC_ANALYTICS_ENDPOINT=📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Generic analytics collector endpoint that accepts JSON POST payloads. | |
| # Leave empty to disable the built-in pageview and click tracking. | |
| PUBLIC_ANALYTICS_ENDPOINT= | |
| # Generic analytics collector endpoint that accepts JSON POST payloads. | |
| # This value is exposed to browsers; do not include secrets or private write tokens. | |
| # Leave empty to disable the built-in pageview and click tracking. | |
| PUBLIC_ANALYTICS_ENDPOINT= |
🤖 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/.env.example` around lines 15 - 17, The analytics endpoint value is
emitted into the page by index.astro, so the .env.example entry should
explicitly warn that PUBLIC_ANALYTICS_ENDPOINT is client-visible and must not
contain secrets or tokens. Update the comment near PUBLIC_ANALYTICS_ENDPOINT to
call out that anything embedded in the URL will be exposed to visitors, and keep
the guidance aligned with how index.astro consumes this setting.
| react(), | ||
| starlight({ | ||
| title: 'TouchAI', | ||
| disable404Route: true, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Astro custom 404 route candidates:"
fd -a '^404\.(astro|md|mdx)$' apps/site/src/pages || true
echo
echo "Starlight docs 404 candidates:"
fd -a '^404\.(md|mdx)$' apps/site/src/content/docs || trueRepository: TouchAI-org/TouchAI
Length of output: 277
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Astro/Starlight config and 404-related files:"
git ls-files 'apps/site/**' | rg '(^|/)(astro\.config\.mjs|404\.(astro|md|mdx)|starlight|pages/404|content/docs/404)' || true
echo
echo "Search for disable404Route usage:"
rg -n "disable404Route|404\\.md|404\\.astro" apps/site || true
echo
echo "Inspect the site astro config:"
cat -n apps/site/astro.config.mjsRepository: TouchAI-org/TouchAI
Length of output: 972
Keep Starlight’s 404 route enabled
apps/site/src/content/docs/404.md only customizes the injected Starlight 404; there’s no src/pages/404.* in this tree, so disable404Route: true removes the only 404 route. Remove this flag or add a real Astro 404 page.
🤖 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/astro.config.mjs` at line 13, The site config is disabling the only
available 404 route, since `apps/site/src/content/docs/404.md` is just the
Starlight-injected 404 and there is no real `src/pages/404.*` page. Update
`astro.config.mjs` by removing `disable404Route` from the Starlight config, or
alternatively add an actual Astro 404 page if you want to keep the flag; use the
`disable404Route` setting and the existing `404.md` customization as the key
places to fix.
| function escapeHtml(value) { | ||
| return renderer ? renderer.escapeHtml(value) : String(value); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Keep escaping safe when the base renderer is unavailable.
The fallback currently returns raw String(value), so loading this utility before touchai-lite-renderer.js turns formula/text rendering into raw HTML insertion.
🛡️ Proposed fix
function escapeHtml(value) {
- return renderer ? renderer.escapeHtml(value) : String(value);
+ if (renderer) return renderer.escapeHtml(value);
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"');
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function escapeHtml(value) { | |
| return renderer ? renderer.escapeHtml(value) : String(value); | |
| } | |
| function escapeHtml(value) { | |
| if (renderer) return renderer.escapeHtml(value); | |
| return String(value) | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"'); | |
| } |
🤖 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/demo-utils/touchai-lite-math.js` around lines 4 - 6, The
escapeHtml helper currently falls back to raw String(value) when renderer is
unavailable, which bypasses HTML escaping in touchai-lite-math.js. Update
escapeHtml so the no-renderer path still safely escapes the value instead of
returning unescaped text, and keep the renderer.escapeHtml branch unchanged; use
the escapeHtml function in touchai-lite-math.js as the target for the fix.
| strict: 'ignore', | ||
| trust: false, | ||
| }); | ||
| } catch (error) {} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Make the KaTeX fallback catch lint-clean.
The empty catch with an unused error binding is reported by ESLint. Prefer catch { /* fall back to lightweight renderer */ }.
🧰 Tools
🪛 ESLint
[error] 29-29: 'error' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 29-29: Empty block statement.
(no-empty)
🤖 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/demo-utils/touchai-lite-math.js` at line 29, The KaTeX
fallback path has an empty catch block with an unused error binding, which
triggers the lint warning. Update the catch in the lightweight renderer fallback
so it uses a binding-free catch form, and keep the fallback behavior in place
within the same KaTeX rendering logic.
Source: Linters/SAST tools
| return escapeHtml(segment).replace( | ||
| /\*\*([^*]+)\*\*/g, | ||
| '<strong>$1</strong>' | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Apply the formatter to satisfy Prettier.
Static analysis reports Prettier errors in these two wrapped expressions; this can fail lint even though behavior is correct.
Also applies to: 51-53
🧰 Tools
🪛 ESLint
[error] 26-29: Replace ⏎····················/\*\*([^*]+)\*\*/g,⏎····················'<strong>$1</strong>'⏎················ with /\*\*([^*]+)\*\*/g,·'<strong>$1</strong>'
(prettier/prettier)
🤖 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/demo-utils/touchai-lite-renderer.js` around lines 26 - 29,
Prettier is flagging the wrapped replace expressions in the renderer, so update
the formatting in touchai-lite-renderer’s markdown handling to match the
project’s style without changing behavior. Reflow the chained
escapeHtml(...).replace(...) call and the similar wrapped expression later in
the file so they satisfy Prettier, keeping the same regex and replacement logic.
Source: Linters/SAST tools
| <span class="sr-only" data-i18n="hero.heading"> | ||
| TouchAI:一触即达的桌面效率 Agent | ||
| </span> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add the missing hero.heading translation.
The hidden <h1> text has data-i18n="hero.heading", but neither language dictionary defines that key, so switching to English leaves the screen-reader heading in Chinese.
Proposed fix
zh: {
+ 'hero.heading': 'TouchAI:一触即达的桌面效率 Agent',
'nav.features': '功能',
@@
en: {
+ 'hero.heading': 'TouchAI: A Desktop Efficiency Agent at Your Fingertips',
'nav.features': 'Features',Also applies to: 2333-2454, 2619-2623
🤖 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 1889 - 1891, The hidden heading
uses data-i18n="hero.heading" in the hero section, but the translation key is
missing from both language dictionaries. Add hero.heading to the existing i18n
dictionaries used by the page (the same structures that define the other hero
strings in index.astro) so the screen-reader <h1> resolves correctly in both
languages instead of falling back to Chinese.
| const queueDemoScrollIdle = (frame) => { | ||
| if (!frame) return; | ||
| requestAnimationFrame(() => { | ||
| sendToDemo(frame, { type: 'touchai-clear-scroll-driven' }); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Align touchai-clear-scroll-driven with the host reset contract.
This page sends touchai-clear-scroll-driven, but the host runtime only clears is-scroll-driven for *-start and touchai-reset-demo. Cleanup after a mid-progress trigger can leave the host scroll-driven class/data state stuck.
Proposed cross-file fix
const isScrollDrivenResetType = (messageType: string) =>
- messageType.endsWith('-start') || messageType === 'touchai-reset-demo';
+ messageType.endsWith('-start') ||
+ messageType === 'touchai-clear-scroll-driven' ||
+ messageType === 'touchai-reset-demo';Also applies to: 3511-3512, 3581-3586
🤖 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 2515 - 2519, The demo cleanup
path is sending touchai-clear-scroll-driven, but the host runtime only clears
the scroll-driven state through its reset contract for *-start and
touchai-reset-demo. Update the cleanup flow in queueDemoScrollIdle and the
related sendToDemo callers so the host receives the same reset signal it already
understands, and make the matching host-side handler clear is-scroll-driven/data
state there instead of relying on the unsupported clear event.
| const sendAnalytics = (type, detail = {}) => { | ||
| if (!analyticsEndpoint) return; | ||
| const payload = JSON.stringify({ | ||
| type, | ||
| href: window.location.href, | ||
| path: window.location.pathname, | ||
| title: document.title, | ||
| lang: document.documentElement.lang, | ||
| referrer: document.referrer || '', | ||
| viewport: `${window.innerWidth}x${window.innerHeight}`, | ||
| ts: new Date().toISOString(), | ||
| ...detail, | ||
| }); | ||
|
|
||
| try { | ||
| if (navigator.sendBeacon) { | ||
| const sent = navigator.sendBeacon( | ||
| analyticsEndpoint, | ||
| new Blob([payload], { type: 'application/json' }) | ||
| ); | ||
| if (sent) return; | ||
| } | ||
| } catch (_error) { | ||
| // Fall through to fetch. | ||
| } | ||
|
|
||
| fetch(analyticsEndpoint, { | ||
| method: 'POST', | ||
| headers: { 'content-type': 'application/json' }, | ||
| body: payload, | ||
| keepalive: true, | ||
| }).catch(() => {}); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Avoid sending full URLs and referrers to analytics.
window.location.href and document.referrer can include query strings, fragments, or same-origin sensitive paths. Send only minimized fields unless the collector explicitly needs more.
Proposed fix
const sendAnalytics = (type, detail = {}) => {
if (!analyticsEndpoint) return;
+ const pageUrl = new URL(window.location.href);
+ const referrerOrigin = (() => {
+ try {
+ return document.referrer ? new URL(document.referrer).origin : '';
+ } catch (_error) {
+ return '';
+ }
+ })();
const payload = JSON.stringify({
type,
- href: window.location.href,
- path: window.location.pathname,
+ origin: pageUrl.origin,
+ path: pageUrl.pathname,
title: document.title,
lang: document.documentElement.lang,
- referrer: document.referrer || '',
+ referrerOrigin,
viewport: `${window.innerWidth}x${window.innerHeight}`,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const sendAnalytics = (type, detail = {}) => { | |
| if (!analyticsEndpoint) return; | |
| const payload = JSON.stringify({ | |
| type, | |
| href: window.location.href, | |
| path: window.location.pathname, | |
| title: document.title, | |
| lang: document.documentElement.lang, | |
| referrer: document.referrer || '', | |
| viewport: `${window.innerWidth}x${window.innerHeight}`, | |
| ts: new Date().toISOString(), | |
| ...detail, | |
| }); | |
| try { | |
| if (navigator.sendBeacon) { | |
| const sent = navigator.sendBeacon( | |
| analyticsEndpoint, | |
| new Blob([payload], { type: 'application/json' }) | |
| ); | |
| if (sent) return; | |
| } | |
| } catch (_error) { | |
| // Fall through to fetch. | |
| } | |
| fetch(analyticsEndpoint, { | |
| method: 'POST', | |
| headers: { 'content-type': 'application/json' }, | |
| body: payload, | |
| keepalive: true, | |
| }).catch(() => {}); | |
| const sendAnalytics = (type, detail = {}) => { | |
| if (!analyticsEndpoint) return; | |
| const pageUrl = new URL(window.location.href); | |
| const referrerOrigin = (() => { | |
| try { | |
| return document.referrer ? new URL(document.referrer).origin : ''; | |
| } catch (_error) { | |
| return ''; | |
| } | |
| })(); | |
| const payload = JSON.stringify({ | |
| type, | |
| origin: pageUrl.origin, | |
| path: pageUrl.pathname, | |
| title: document.title, | |
| lang: document.documentElement.lang, | |
| referrerOrigin, | |
| viewport: `${window.innerWidth}x${window.innerHeight}`, | |
| ts: new Date().toISOString(), | |
| ...detail, | |
| }); | |
| try { | |
| if (navigator.sendBeacon) { | |
| const sent = navigator.sendBeacon( | |
| analyticsEndpoint, | |
| new Blob([payload], { type: 'application/json' }) | |
| ); | |
| if (sent) return; | |
| } | |
| } catch (_error) { | |
| // Fall through to fetch. | |
| } | |
| fetch(analyticsEndpoint, { | |
| method: 'POST', | |
| headers: { 'content-type': 'application/json' }, | |
| body: payload, | |
| keepalive: true, | |
| }).catch(() => {}); |
🤖 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 2718 - 2749, The analytics
payload built in sendAnalytics currently includes full window.location.href and
document.referrer values, which may expose query strings, fragments, or
sensitive paths. Update the payload construction to use minimized
location/referrer data instead of the full URL fields, keeping the rest of the
sendAnalytics logic unchanged and preserving the same event metadata shape where
possible.
| if (messageType) { | ||
| const existingIndex = pendingMessages.findIndex( | ||
| (pendingMessage) => getMessageType(pendingMessage) === messageType | ||
| ); | ||
| if (existingIndex >= 0) { | ||
| pendingMessages[existingIndex] = data; | ||
| } else { | ||
| pendingMessages.push(data); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve pending-message order when deduping by type.
Replacing an earlier queued message in place can invert the final order. For example: progress → clear → progress flushes as latest progress then stale clear. Move replaced messages to the tail so the queue reflects the latest chronology.
Proposed fix
if (messageType) {
const existingIndex = pendingMessages.findIndex(
(pendingMessage) => getMessageType(pendingMessage) === messageType
);
if (existingIndex >= 0) {
- pendingMessages[existingIndex] = data;
+ pendingMessages.splice(existingIndex, 1);
+ pendingMessages.push(data);
} else {
pendingMessages.push(data);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (messageType) { | |
| const existingIndex = pendingMessages.findIndex( | |
| (pendingMessage) => getMessageType(pendingMessage) === messageType | |
| ); | |
| if (existingIndex >= 0) { | |
| pendingMessages[existingIndex] = data; | |
| } else { | |
| pendingMessages.push(data); | |
| } | |
| if (messageType) { | |
| const existingIndex = pendingMessages.findIndex( | |
| (pendingMessage) => getMessageType(pendingMessage) === messageType | |
| ); | |
| if (existingIndex >= 0) { | |
| pendingMessages.splice(existingIndex, 1); | |
| pendingMessages.push(data); | |
| } else { | |
| pendingMessages.push(data); | |
| } |
🤖 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 490 - 498, The
pending-message dedupe logic in component-demo-runtime’s message queue is
replacing an older item in place, which can change the final flush order. Update
the branch that uses getMessageType, pendingMessages.findIndex, and
pendingMessages[existingIndex] so that when a matching messageType is found, the
old entry is removed and the new data is appended to the end of pendingMessages
instead of overwriting in place. Keep the existing push behavior for new types
so the queue preserves latest chronology.
| const startLoad = () => { | ||
| if (host.dataset.touchaiLoadStarted === 'true') return Promise.resolve(); | ||
| host.dataset.touchaiLoadStarted = 'true'; | ||
| return load(); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Allow lazy-load retries after a failed first load.
touchaiLoadStarted stays true even if load() fails, so future host.load?.() calls no-op and the lazy demo cannot recover from a transient fetch error. Track an in-flight promise or clear the flag on failure.
🤖 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 880 - 883, The
lazy-load guard in startLoad currently leaves host.dataset.touchaiLoadStarted
set to true even when load() rejects, which blocks later host.load?.() retries.
Update the startLoad flow in component-demo-runtime so it either tracks the
in-flight promise and reuses it, or resets touchaiLoadStarted in the failure
path before rethrowing, ensuring the lazy demo can retry after a transient load
error.
Summary
Refreshes the landing page demos and repository stats, then adjusts the mobile ScrollTrigger start point so sections animate at a better viewport position on narrow screens.
Related issue or RFC
N/A. Focused site polish and lockfile sync.
AI assistance disclosure
Testing evidence
Results:
corepack pnpm test:prpassed.commitlintpassed.try_lock_mutexwarnings were reported; no errors failed the run.Risk notes
AgentService, runtime, MCP, or schema impact: noneapps/desktop/src-tauri/Cargo.locknow matches the existingCargo.tomlpackage versionScreenshots or recordings
N/A.
Checklist
[WIP]or similar title prefixes.AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.pnpm test:prfor this code PR, or this is a docs-only change.pnpm test:coverage:rustor relied on CI coverage evidence.pnpm test:e2elocally or documented why CI is the first valid proof.