Skip to content

feat(docs): complete docs site with docs-kit, dark mode, and full pages#3

Merged
jaysin586 merged 14 commits into
mainfrom
fix/add-license-and-publish-readiness
Apr 15, 2026
Merged

feat(docs): complete docs site with docs-kit, dark mode, and full pages#3
jaysin586 merged 14 commits into
mainfrom
fix/add-license-and-publish-readiness

Conversation

@jaysin586

Copy link
Copy Markdown
Contributor

Summary

The docs-kit rebuild, dark mode support, full documentation pages, OG image generation, and cross-marketing were all pushed to the branch after PR #2 was merged. This PR brings those 7 commits into main so the Cloudflare deploy picks them up.

Changes

  • Rebuild docs with @humanspeak/docs-kit (RootLayout, Header, Footer, dark mode, SEO)
  • Add 7 docs pages via mdsvex + shiki (Getting Started, API, Props, Imperative, 3 Guides)
  • Add live looping conversation demo on landing page
  • Add OG social card generation + /social-cards route
  • Add sitemap.xml route, llms-full.txt, robots.txt
  • Convert all 3 example components to dark mode semantic tokens
  • Add cross-marketing callout to streaming example
  • Add enhanceCodeBlocks, CSP headers, prettier config, vite chunking
  • Add GitHub stats fetch script to build pipeline
  • Bump wrangler compatibility_date to 2026-04-07

Commits

  • 128cf95 build(docs): bump wrangler compatibility_date to 2026-04-07
  • 40279f1 fix(docs): convert all examples to dark mode with semantic tokens
  • fa685de docs: add cross-marketing callout to streaming example
  • fd4aa0c fix(docs): close all deltas with svelte-markdown docs site
  • 859b4d4 feat(docs): add dynamic sitemap.xml route
  • dca416e feat(docs): add full docs pages, OG generation, live conversation demo
  • e6ceb73 feat(docs): rebuild docs site with @humanspeak/docs-kit

jaysin586 and others added 7 commits April 14, 2026 18:01
Replace barebones docs with production-quality site matching
svelte-markdown's architecture:

- Integrate @humanspeak/docs-kit for RootLayout, Header, Footer,
  SEO context, breadcrumbs, dark mode, and theme system
- Add docs-config.ts, docsNav.ts, icons.ts, Example.svelte
- Rebuild landing page with hero, features grid, code example,
  and examples CTA section
- Add SEO context to all example pages
- Add hooks.server.ts, robots.txt, llms.txt, logo.svg
- Switch tsconfig to moduleResolution: bundler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add mdsvex + shiki for docs with dual-theme syntax highlighting
- Add DocsLayout with sidebar navigation and other-projects
- Create 7 docs pages: Getting Started, SvelteVirtualChat API,
  Props Reference, Imperative API, LLM Streaming Guide,
  History Loading Guide, Scroll Behavior Guide
- Add /examples index page with card grid
- Replace single-message demo with looping 5-question conversation
- Add OG social card generation via docs-kit
- Add sitemap:watch and social:watch to mprocs
- Fix API Reference button, dead links, fetchOtherProjects error handling
- Update icon to green chat bubble, fix theme-color

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generates sitemap from page routes with lastmod from manifest,
priority by section, and monthly changefreq. Fixes 404 referenced
by robots.txt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. enhanceCodeBlocks — add to examples layout for copy buttons
2. GitHub stats fetch — add fetch-github-stats.ts script, wire
   into build pipeline so stars update on each deploy
3. CSP headers — add Content-Security-Policy directives to
   svelte.config.js (hash mode, strict defaults)
4. Prettier config — add .prettierrc and .prettierignore to docs/
5. llms-full.txt — comprehensive LLM reference document with
   all props, types, methods, usage patterns, and doc links
6. Social cards route — add /social-cards with OG.svelte for
   dynamic Open Graph image generation
7. Vite chunking — isolate shiki and marked into separate chunks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add "How It Works" box linking to @humanspeak/svelte-markdown
(streaming mode), ModelPricing.ai (token costs), and
@humanspeak/svelte-virtual-list — mirroring the cross-promotion
svelte-markdown gives us on their LLM streaming page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded light-mode colors (bg-white, text-gray-900,
border-gray-200, bg-indigo-50, etc.) with docs-kit semantic tokens
(bg-card, text-foreground, border-border, bg-brand-500/5, etc.)
across all three example components:

- BasicChat: controls, stats, chat viewport, messages, input
- StreamingChat: metrics, controls, how-it-works callout, stats,
  chat viewport, messages, markdown prose
- HistoryLoading: info bar, stats, controls, chat viewport, messages

All examples now respect dark/light mode automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

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 a docs site integration using @humanspeak/docs-kit: new docs config, icons, CSS/theme-token changes, mdsvex + shiki highlighter, CSP and Vite chunking, sitemap and social-card generators and watch/build scripts, robots/llms files, and many new Svelte layouts, components, and doc pages. Introduces server routes (sitemap.xml), hooks.server, social-cards route/layout, and scripts to fetch GitHub stats and generate social cards. Updates Playwright tests and adds runtime behavior changes to SvelteVirtualChat (user-scroll suppression for snaps and several exported functions converted to const arrow forms). Misc config edits (tsconfig, svelte.config, vite, wrangler, package.json).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main changes: rebuilding the docs site with docs-kit, adding dark mode, and implementing full documentation pages.
Description check ✅ Passed Description directly relates to the changeset, providing a structured summary of changes, commit history, and implementation details for the docs site rebuild.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@blacksmith-sh

blacksmith-sh Bot commented Apr 15, 2026

Copy link
Copy Markdown

Found 1 test error on Blacksmith runners:

Error

Test View Logs
chat/scroll-away.spec.ts/
Append While Scrolled Away › starts with 50 messages following bottom
View Logs

Fix in Cursor

When the user scrolls up while at bottom, programmatic snaps from
ResizeObserver measurements and totalHeight changes were overriding
the user's scroll in a feedback loop — making it difficult or
impossible to scroll away.

Fix: add a userScrolling flag set by the scroll handler with a
150ms debounce timer. All snap paths (scheduleSnapToBottom,
messages.length effect, totalHeight effect) now check this flag
and skip snaps while the user is actively scrolling.

Also:
- Add e2e test: "user can scroll up from bottom without being
  snapped back" — simulates incremental wheel scroll and asserts
  the viewport escapes follow-bottom
- Adjust rapid-append test timing for the new suppression window
- Add .coderabbit.yaml from svelte-markdown

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@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: 10

Caution

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

⚠️ Outside diff range comments (1)
docs/src/lib/examples/StreamingChat.svelte (1)

86-90: ⚠️ Potential issue | 🟡 Minor

Consider cleanup on component unmount to prevent timeout leaks.

The timeoutId is cleared in stopStreaming(), but there's no guarantee this runs when the component unmounts. If the user navigates away mid-stream, the timeout could fire after unmount.

Suggested fix: add $effect cleanup

Add an effect at the end of the script to ensure cleanup:

$effect(() => {
    return () => {
        if (timeoutId) clearTimeout(timeoutId)
    }
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/lib/examples/StreamingChat.svelte` around lines 86 - 90, Add a
component unmount cleanup to clear the pending timeout to avoid leaks: in the
same script where timeoutId and stopStreaming() are declared, add a $effect that
returns a cleanup function which checks timeoutId, calls clearTimeout(timeoutId)
and sets timeoutId to null; this ensures any pending timeout is cleared if the
component unmounts even when stopStreaming() isn't invoked.
🧹 Nitpick comments (8)
docs/src/routes/social-cards/+layout.svelte (1)

1-2: Type $props() explicitly for strict safety.

children is untyped at Line 2, which weakens type safety in a lang="ts" Svelte file.

Suggested patch
 <script lang="ts">
-    const { children } = $props()
+    import type { Snippet } from 'svelte'
+
+    const { children }: { children?: Snippet } = $props()
 </script>

As per coding guidelines, **/*.{ts,tsx,svelte}: Use TypeScript with strict mode enabled for full type safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/social-cards/`+layout.svelte around lines 1 - 2, The
destructured children is untyped; update the $props() call to use an explicit
generic props interface so TypeScript enforces strict types (e.g., define an
interface like LayoutProps with a typed children property and call
$props<LayoutProps>() before destructuring). Locate the const { children } =
$props() in +layout.svelte and replace it with a typed $props usage (using the
interface/type name you add) so children has an explicit type under strict mode.
docs/src/routes/docs/api/imperative/+page.svx (2)

6-18: Add lang="ts" to script tag for consistency.

Other documentation pages and components in this PR use TypeScript. Adding lang="ts" ensures consistency across the codebase and enables type checking for the SEO context usage.

Suggested fix
-<script>
+<script lang="ts">
     import { getSeoContext } from '@humanspeak/docs-kit'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/docs/api/imperative/`+page.svx around lines 6 - 18, Add
TypeScript to the Svelte script by updating the <script> tag to include
lang="ts" so getSeoContext() and the seo object are type-checked; ensure the
existing import of getSeoContext and the seo assignment block (the seo variable
and properties: title, description, ogTitle, ogTagline, ogFeatures, ogSlug)
remain unchanged but now live inside a <script lang="ts"> section to match other
docs pages and enable TS tooling.

55-59: Clarify scrollToMessage options parameter structure.

The table shows smooth as a direct param, but the actual API wraps it in an options object (options?: { smooth?: boolean }). The code example above is correct, but the table could be clearer.

Suggested clarification
 | Param | Type | Description |
 |-------|------|-------------|
 | `id` | `string` | The message ID (as returned by `getMessageId`) |
-| `smooth` | `boolean` | Use smooth scrolling animation |
+| `options.smooth` | `boolean` | Use smooth scrolling animation |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/docs/api/imperative/`+page.svx around lines 55 - 59, Update
the API docs table for scrollToMessage to show that the second parameter is an
options object: change the param name from `smooth` to `options` and the type to
` { smooth?: boolean }` with a description like "Options object; set smooth to
true for smooth scrolling"; keep the `id` row as-is and ensure you reference the
scrollToMessage API and getMessageId in the surrounding text so examples remain
consistent with the code sample.
docs/src/routes/docs/api/props/+page.svx (1)

6-18: Add lang="ts" to script tag for consistency.

Same as the imperative API page - adding TypeScript consistency across documentation pages.

Suggested fix
-<script>
+<script lang="ts">
     import { getSeoContext } from '@humanspeak/docs-kit'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/docs/api/props/`+page.svx around lines 6 - 18, Update the
top-level <script> tag to use TypeScript by adding lang="ts" (i.e., change the
current <script> to <script lang="ts">) so the getSeoContext import and the seo
assignments in this file use TypeScript parsing consistent with the other docs
pages; ensure any TypeScript-specific syntax in the block is valid after the
change and adjust imports or types if needed for getSeoContext or seo usage.
docs/src/routes/+page.svelte (1)

349-371: "Restart" and "Reset" buttons have identical behavior.

Both buttons on lines 350-356 and 365-371 call resetDemo(). This seems redundant. Consider either:

  1. Removing one button
  2. Differentiating behavior (e.g., "Restart" continues from current state, "Reset" clears everything)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/`+page.svelte around lines 349 - 371, The "Restart" and
"Reset" buttons both call resetDemo(), causing duplicate behavior; update the
markup to give them distinct actions: either remove one button or change one
handler (e.g., have Restart call a new or existing startDemo/restartDemo
function and keep Reset calling resetDemo) and adjust any related state logic in
the corresponding functions; locate the two buttons referencing resetDemo (the
elements rendering Play and RotateCw) and wire the intended handler (stopDemo is
correct for the Stop button) or delete the redundant element.
docs/src/routes/examples/+layout.svelte (1)

11-20: Consider using $effect for initial breadcrumb setup.

Setting breadcrumbContext.breadcrumbs directly during component initialization (lines 12-14) is a side effect during render. While it works, Svelte 5 prefers side effects in $effect blocks. The afterNavigate hook handles subsequent navigations correctly.

Suggested approach
+    import { untrack } from 'svelte'
+
     const breadcrumbContext = getBreadcrumbContext()
-    if (breadcrumbContext) {
-        breadcrumbContext.breadcrumbs = buildBreadcrumbs(page.url.pathname)
-    }
+
+    $effect(() => {
+        if (breadcrumbContext) {
+            breadcrumbContext.breadcrumbs = buildBreadcrumbs(page.url.pathname)
+        }
+    })

-    afterNavigate(() => {
-        if (breadcrumbContext) {
-            breadcrumbContext.breadcrumbs = buildBreadcrumbs(page.url.pathname)
-        }
-    })

This consolidates both initial and navigation updates into a single reactive effect that runs when page.url.pathname changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/examples/`+layout.svelte around lines 11 - 20, Move the
initial breadcrumb assignment into a Svelte $effect so side effects run outside
render: remove the direct assignment to breadcrumbContext.breadcrumbs in the
top-level initialization and instead create a single $effect that (1) obtains
getBreadcrumbContext() if needed, (2) sets breadcrumbContext.breadcrumbs =
buildBreadcrumbs(page.url.pathname), and (3) subscribes to navigation changes
(or reacts to page.url.pathname) so you can drop the separate afterNavigate
handler; reference getBreadcrumbContext, buildBreadcrumbs, afterNavigate,
page.url.pathname, and breadcrumbContext.breadcrumbs when implementing the
$effect.
docs/src/lib/components/shared-link/OG.svelte (1)

5-17: Type definition doesn't reflect default value for type.

The type prop has a default value of 'og' (line 6), but the type definition on line 12 doesn't mark it as optional. This is technically fine at runtime, but the type signature could be clearer.

Optional: align type with default
     }: {
-        type: 'og' | 'twitter'
+        type?: 'og' | 'twitter'
         url: string
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/lib/components/shared-link/OG.svelte` around lines 5 - 17, The prop
type for the destructured props block currently declares type: 'og' | 'twitter'
but you provide a default ('og')—make the signature reflect that by marking the
prop optional or allowing undefined: update the type annotation in the
destructuring (the "type" prop in the const { ... }: { ... } = $props()) to
type?: 'og' | 'twitter' (or 'og' | 'twitter' | undefined) so the static type
matches the runtime default.
docs/svelte.config.js (1)

44-49: Remove 'unsafe-inline' from script-src for cleaner CSP configuration.

With mode: 'hash', SvelteKit automatically adds SHA256 hashes for generated inline scripts, making 'unsafe-inline' unnecessary. While modern browsers ignore 'unsafe-inline' when hashes are present, removing it makes the policy more explicit and aligns with CSP best practices.

Suggested fix
         csp: {
             mode: 'hash',
             directives: {
                 'default-src': ['self'],
-                'script-src': ['self', 'unsafe-inline', 'wasm-unsafe-eval'],
+                'script-src': ['self', 'wasm-unsafe-eval'],
                 'style-src': ['self', 'unsafe-inline'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/svelte.config.js` around lines 44 - 49, The CSP configuration in the
exported csp object still includes 'unsafe-inline' in the 'script-src' directive
even though mode: 'hash' is used; remove 'unsafe-inline' from the 'script-src'
array in the csp directives so SvelteKit can rely on generated SHA256 hashes
instead. Locate the csp config (csp: { mode: 'hash', directives: { ... } }) and
edit the 'script-src' entry to only include allowed sources (e.g., 'self' and
'wasm-unsafe-eval' or other needed origins) removing the 'unsafe-inline' token
to make the policy explicit and follow CSP best practices. Ensure no other parts
of the config or comments re-add 'unsafe-inline'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/src/app.css`:
- Around line 2-4: Update the Stylelint config to whitelist Tailwind v4 custom
at-rules so stylelint won't flag `@source` and `@plugin` in docs/src/app.css: in
.stylelintrc.json add/modify the "scss/at-rule-no-unknown" rule to include an
"ignoreAtRules" array containing "source", "plugin", "config", "theme",
"utility", and "variant", ensuring the existing config still extends
"stylelint-config-standard-scss".

In `@docs/src/lib/components/general/Example.svelte`:
- Around line 51-57: The reset button using the RotateCw icon lacks an explicit
accessible name and an explicit button type; update the button element (the one
with onclick={refresh} and child RotateCw) to include an aria-label (e.g.,
aria-label="Reset example") and add type="button" to ensure correct semantics
and keyboard/screen-reader accessibility while keeping the existing title if
desired.

In `@docs/src/routes/docs/`+layout.svelte:
- Line 7: The destructuring const { children, data } = $props() lacks the
generated route prop type and should be annotated and declared with let for
Svelte 5 convention; import the route props type from the route's generated
$types module, change the declaration to use let with that type on the
destructured pattern (referencing $props(), children, data and the generated
route prop type), and ensure the import of the type from the module is added at
the top of the file.

In `@docs/src/routes/docs/api/props/`+page.svx:
- Around line 109-114: The `debug` prop is currently declared in the type
definition but not used by the component and docs conflict; pick one approach
and make code/docs consistent: either implement the prop by adding `export let
debug = false` (or similar) to SvelteVirtualChat.svelte and wire it to enable
console/logging branches where appropriate (and update README and API docs to
state "Enable console debug logging"), or remove `debug` from the type in
src/lib/types.ts and delete references in README and API docs so all docs state
it's unused/removed; ensure modifications reference the `debug` prop,
`SvelteVirtualChat.svelte`, and the type in `src/lib/types.ts` so the change is
applied everywhere.

In `@docs/src/routes/docs/guides/llm-streaming/`+page.svx:
- Around line 78-80: The snippet uses ANTHROPIC_API_KEY but never imports it;
add the SvelteKit environment import so the server example compiles. Import
ANTHROPIC_API_KEY from the appropriate SvelteKit env module (e.g.
'$env/static/private' or '$env/dynamic/private') at the top of the file and then
pass that constant into new Anthropic({ apiKey: ANTHROPIC_API_KEY }) to replace
the undefined identifier; update the file containing the import and the const
client = new Anthropic(...) usage.
- Around line 119-130: The loop decodes streamed UTF-8 with
decoder.decode(value, { stream: true }) but never flushes the decoder at the
end, which can drop a trailing partial character; after the reader loop that
uses response.body!.getReader(), call decoder.decode() once with no arguments to
flush any buffered bytes and append that result to the same message (the msg
found via messages.find(m => m.id === messageId)) if it exists so the final
characters are preserved.

In `@docs/src/routes/docs/guides/scroll-behavior/`+page.svx:
- Around line 44-63: The example uses chat?.scrollToBottom(...) but never
defines or binds the chat component reference; add a local variable (e.g. let
chat) in the <script lang="ts"> block and bind the SvelteVirtualChat component
instance to it using bind:this so the scrollToBottom method is callable; ensure
the variable name matches the bind (chat) and that you reference the
SvelteVirtualChat instance when calling scrollToBottom.

In `@docs/src/routes/sitemap.xml/`+server.ts:
- Around line 32-34: The routes array currently filters page paths but doesn't
exclude SvelteKit dynamic segments like [slug], so placeholder paths can end up
in the sitemap; update the filter used where routes is computed (the expression
building routes from pageFiles.map(toPath)) and the identical pattern later to
also exclude bracketed dynamic segments by extending the regex to reject paths
containing /\[.*?\]/ (i.e., add a negative check for /\[.*?\]/ alongside the
existing /\/_(?:.*)|\/(?:\+|__)/ test) so dynamic route placeholders are omitted
from the sitemap.

In `@docs/src/routes/social-cards/`+page.svelte:
- Around line 5-7: The code is casting the query param to 'og'|'twitter' which
lets invalid strings through; change the logic that computes cardType (the
$derived call that uses page.url.searchParams.get('type')) to perform a runtime
validation: read the raw string, check it against the allowed set
['og','twitter'] and return the matching union value or 'og' as a default, then
use that validated cardType when rendering the OG/Twitter component (avoid using
"as 'og' | 'twitter'"). Ensure the runtime check is colocated with the existing
derived computation so all downstream consumers (e.g., the OG component) only
receive the validated union.

In `@docs/svelte.config.js`:
- Around line 6-9: The highlighter is missing the 'text' language and doesn't
validate fence labels, causing codeToHtml(...) to fail for unlabeled or
unsupported fences; update the createHighlighter call by adding 'text' to the
langs array and modify the code that invokes highlighter/codeToHtml to check the
requested lang against the highlighter's supported languages (or fallback to
'text') before calling codeToHtml — specifically adjust the
createHighlighter(...) langs list and add a guard around where
highlighter.codeToHtml or highlighter is called to normalize/validate the lang
string and use 'text' when the label is absent or unsupported.

---

Outside diff comments:
In `@docs/src/lib/examples/StreamingChat.svelte`:
- Around line 86-90: Add a component unmount cleanup to clear the pending
timeout to avoid leaks: in the same script where timeoutId and stopStreaming()
are declared, add a $effect that returns a cleanup function which checks
timeoutId, calls clearTimeout(timeoutId) and sets timeoutId to null; this
ensures any pending timeout is cleared if the component unmounts even when
stopStreaming() isn't invoked.

---

Nitpick comments:
In `@docs/src/lib/components/shared-link/OG.svelte`:
- Around line 5-17: The prop type for the destructured props block currently
declares type: 'og' | 'twitter' but you provide a default ('og')—make the
signature reflect that by marking the prop optional or allowing undefined:
update the type annotation in the destructuring (the "type" prop in the const {
... }: { ... } = $props()) to type?: 'og' | 'twitter' (or 'og' | 'twitter' |
undefined) so the static type matches the runtime default.

In `@docs/src/routes/`+page.svelte:
- Around line 349-371: The "Restart" and "Reset" buttons both call resetDemo(),
causing duplicate behavior; update the markup to give them distinct actions:
either remove one button or change one handler (e.g., have Restart call a new or
existing startDemo/restartDemo function and keep Reset calling resetDemo) and
adjust any related state logic in the corresponding functions; locate the two
buttons referencing resetDemo (the elements rendering Play and RotateCw) and
wire the intended handler (stopDemo is correct for the Stop button) or delete
the redundant element.

In `@docs/src/routes/docs/api/imperative/`+page.svx:
- Around line 6-18: Add TypeScript to the Svelte script by updating the <script>
tag to include lang="ts" so getSeoContext() and the seo object are type-checked;
ensure the existing import of getSeoContext and the seo assignment block (the
seo variable and properties: title, description, ogTitle, ogTagline, ogFeatures,
ogSlug) remain unchanged but now live inside a <script lang="ts"> section to
match other docs pages and enable TS tooling.
- Around line 55-59: Update the API docs table for scrollToMessage to show that
the second parameter is an options object: change the param name from `smooth`
to `options` and the type to ` { smooth?: boolean }` with a description like
"Options object; set smooth to true for smooth scrolling"; keep the `id` row
as-is and ensure you reference the scrollToMessage API and getMessageId in the
surrounding text so examples remain consistent with the code sample.

In `@docs/src/routes/docs/api/props/`+page.svx:
- Around line 6-18: Update the top-level <script> tag to use TypeScript by
adding lang="ts" (i.e., change the current <script> to <script lang="ts">) so
the getSeoContext import and the seo assignments in this file use TypeScript
parsing consistent with the other docs pages; ensure any TypeScript-specific
syntax in the block is valid after the change and adjust imports or types if
needed for getSeoContext or seo usage.

In `@docs/src/routes/examples/`+layout.svelte:
- Around line 11-20: Move the initial breadcrumb assignment into a Svelte
$effect so side effects run outside render: remove the direct assignment to
breadcrumbContext.breadcrumbs in the top-level initialization and instead create
a single $effect that (1) obtains getBreadcrumbContext() if needed, (2) sets
breadcrumbContext.breadcrumbs = buildBreadcrumbs(page.url.pathname), and (3)
subscribes to navigation changes (or reacts to page.url.pathname) so you can
drop the separate afterNavigate handler; reference getBreadcrumbContext,
buildBreadcrumbs, afterNavigate, page.url.pathname, and
breadcrumbContext.breadcrumbs when implementing the $effect.

In `@docs/src/routes/social-cards/`+layout.svelte:
- Around line 1-2: The destructured children is untyped; update the $props()
call to use an explicit generic props interface so TypeScript enforces strict
types (e.g., define an interface like LayoutProps with a typed children property
and call $props<LayoutProps>() before destructuring). Locate the const {
children } = $props() in +layout.svelte and replace it with a typed $props usage
(using the interface/type name you add) so children has an explicit type under
strict mode.

In `@docs/svelte.config.js`:
- Around line 44-49: The CSP configuration in the exported csp object still
includes 'unsafe-inline' in the 'script-src' directive even though mode: 'hash'
is used; remove 'unsafe-inline' from the 'script-src' array in the csp
directives so SvelteKit can rely on generated SHA256 hashes instead. Locate the
csp config (csp: { mode: 'hash', directives: { ... } }) and edit the
'script-src' entry to only include allowed sources (e.g., 'self' and
'wasm-unsafe-eval' or other needed origins) removing the 'unsafe-inline' token
to make the policy explicit and follow CSP best practices. Ensure no other parts
of the config or comments re-add 'unsafe-inline'.
🪄 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: CHILL

Plan: Pro

Run ID: ee461053-b5e8-401c-a6a4-d972f58ef064

📥 Commits

Reviewing files that changed from the base of the PR and between 111727e and 128cf95.

⛔ Files ignored due to path filters (4)
  • docs/src/lib/assets/logo.svg is excluded by !**/*.svg
  • docs/static/og-default.png is excluded by !**/*.png
  • docs/static/twitter-default.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (44)
  • docs/.prettierignore
  • docs/.prettierrc
  • docs/package.json
  • docs/scripts/fetch-github-stats.ts
  • docs/scripts/generate-social-cards.ts
  • docs/src/app.css
  • docs/src/app.html
  • docs/src/hooks.server.ts
  • docs/src/lib/components/general/Example.svelte
  • docs/src/lib/components/shared-link/OG.svelte
  • docs/src/lib/docs-config.ts
  • docs/src/lib/docsNav.ts
  • docs/src/lib/examples/BasicChat.svelte
  • docs/src/lib/examples/HistoryLoading.svelte
  • docs/src/lib/examples/StreamingChat.svelte
  • docs/src/lib/icons.ts
  • docs/src/routes/+layout.svelte
  • docs/src/routes/+layout.ts
  • docs/src/routes/+page.svelte
  • docs/src/routes/docs/+layout.server.ts
  • docs/src/routes/docs/+layout.svelte
  • docs/src/routes/docs/api/imperative/+page.svx
  • docs/src/routes/docs/api/props/+page.svx
  • docs/src/routes/docs/api/svelte-virtual-chat/+page.svx
  • docs/src/routes/docs/getting-started/+page.svx
  • docs/src/routes/docs/guides/history-loading/+page.svx
  • docs/src/routes/docs/guides/llm-streaming/+page.svx
  • docs/src/routes/docs/guides/scroll-behavior/+page.svx
  • docs/src/routes/examples/+layout.svelte
  • docs/src/routes/examples/+page.svelte
  • docs/src/routes/examples/basic-chat/+page.svelte
  • docs/src/routes/examples/history-loading/+page.svelte
  • docs/src/routes/examples/streaming/+page.svelte
  • docs/src/routes/sitemap.xml/+server.ts
  • docs/src/routes/social-cards/+layout.svelte
  • docs/src/routes/social-cards/+page.svelte
  • docs/static/llms-full.txt
  • docs/static/llms.txt
  • docs/static/robots.txt
  • docs/svelte.config.js
  • docs/tsconfig.json
  • docs/vite.config.ts
  • docs/wrangler.jsonc
  • mprocs.yaml

Comment thread docs/src/app.css
Comment on lines +2 to +4
@source '../node_modules/@humanspeak/docs-kit/dist';
@import '@humanspeak/docs-kit/styles/base.css';
@plugin '@tailwindcss/typography';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app.css header =="
nl -ba docs/src/app.css | sed -n '1,20p'

echo
echo "== stylelint config candidates =="
fd -HI 'stylelint|\.stylelintrc'

echo
echo "== stylelint rule snippets (if present) =="
rg -n --iglob '*stylelint*' 'ignoreAtRules|at-rule-no-unknown|no-invalid-position-at-import-rule|scss/at-rule-no-unknown' || true

Repository: humanspeak/svelte-virtual-chat

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app.css content (first 30 lines) =="
head -30 docs/src/app.css || echo "File not found or error reading"

echo
echo "== stylelint config files =="
fd -HI 'stylelint' --type f

echo
echo "== .stylelintrc* files =="
fd -HI '\.stylelintrc' --type f

echo
echo "== stylelint.config files =="
fd -HI 'stylelint\.config' --type f

Repository: humanspeak/svelte-virtual-chat

Length of output: 1027


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== .stylelintrc.json content =="
cat .stylelintrc.json

echo
echo "== Check for at-rule rules in config =="
rg -A5 -B5 'at-rule|ignoreAtRules' .stylelintrc.json || true

Repository: humanspeak/svelte-virtual-chat

Length of output: 1923


🌐 Web query:

stylelint-config-standard-scss default rules at-rule-no-unknown

💡 Result:

In stylelint-config-standard-scss (latest v17.0.0 as of 2026-01-15), the "at-rule-no-unknown" rule is disabled (set to null) and replaced by the SCSS-specific "scss/at-rule-no-unknown" rule, which is enabled (true). This configuration extends stylelint-config-standard (which enables "at-rule-no-unknown": true) and stylelint-config-recommended-scss. The recommended-scss config bundles stylelint-scss plugin and turns on error-checking rules including scss/at-rule-no-unknown, while overriding the standard at-rule-no-unknown to support SCSS at-rules like @use, @extend, @mixin, etc.

Citations:


🌐 Web query:

Tailwind v4 @source @plugin at-rule ordering requirements

💡 Result:

Tailwind CSS v4 does not enforce a strict ordering requirement for @source and @plugin at-rules beyond standard CSS rules for @import statements. All Tailwind-specific at-rules like @source, @plugin, @config, @theme, @utility, and @variant are processed during the Tailwind compilation phase regardless of their position in the CSS file, as long as they appear in the input CSS processed by Tailwind's engine (CLI, Vite plugin, or PostCSS plugin). Standard CSS requires @import rules to precede all other rules, but Tailwind's directives (@source, @plugin, etc.) have no such restriction among themselves. Example valid CSS structure from official docs and examples: @import "tailwindcss"; @theme { --color-brand-500: oklch(62.45% 0.179 264.23); } @source "../node_modules/@my-company/ui-lib"; @plugin "@tailwindcss/typography"; @utility my-utility { margin: 1rem; } @source can appear after @import "tailwindcss" (result 5,3), and @plugin is shown similarly (result 3,7). No sources indicate errors or requirements for specific order between @source and @plugin. Tailwind processes the entire CSS file's directives collectively. Layer ordering (@layer theme, base, components, utilities) is handled automatically by Tailwind's output using native CSS cascade layers, independent of input order (result 5,10,21).

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for scss/at-rule-no-unknown references =="
rg 'scss/at-rule-no-unknown|ignoreAtRules.*source|ignoreAtRules.*plugin' . --type json || echo "Not found - means not configured"

echo
echo "== Check if `@source` and `@plugin` would be flagged =="
echo "Current stylelint config extends stylelint-config-standard-scss"
echo "Which has scss/at-rule-no-unknown ENABLED by default"
echo "Without ignoreAtRules configuration, `@source` and `@plugin` WILL be flagged as unknown"

Repository: humanspeak/svelte-virtual-chat

Length of output: 412


Configure stylelint to recognize Tailwind v4 at-rules before merge.

The stylelint config extends stylelint-config-standard-scss, which has scss/at-rule-no-unknown enabled. This rule will flag @source and @plugin as unknown at-rules, blocking CI/deploys. Add to .stylelintrc.json:

{
  "rules": {
    "scss/at-rule-no-unknown": {
      "ignoreAtRules": ["source", "plugin", "config", "theme", "utility", "variant"]
    }
  }
}

Note: The at-rule ordering in docs/src/app.css is already correct per CSS standards (@import directives first). Tailwind v4 does not enforce ordering constraints between its custom at-rules.

🧰 Tools
🪛 Stylelint (17.7.0)

[error] 3-3: Invalid position for @import rule (no-invalid-position-at-import-rule)

(no-invalid-position-at-import-rule)


[error] 2-2: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 4-4: Unexpected unknown at-rule "@plugin" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/app.css` around lines 2 - 4, Update the Stylelint config to
whitelist Tailwind v4 custom at-rules so stylelint won't flag `@source` and
`@plugin` in docs/src/app.css: in .stylelintrc.json add/modify the
"scss/at-rule-no-unknown" rule to include an "ignoreAtRules" array containing
"source", "plugin", "config", "theme", "utility", and "variant", ensuring the
existing config still extends "stylelint-config-standard-scss".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are Tailwind CSS 4 directives — not standard CSS at-rules. They're processed by the Tailwind Vite plugin. Same pattern as svelte-markdown's docs.

Comment thread docs/src/lib/components/general/Example.svelte
Comment thread docs/src/routes/docs/+layout.svelte Outdated
Comment on lines +109 to +114
### `debug`

- **Type:** `boolean`
- **Default:** `false`

Reserved for future use.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the debug prop is actually used in the component

# Search for debug prop usage in SvelteVirtualChat
rg -n 'debug' src/lib/SvelteVirtualChat.svelte

# Check if debug is destructured from props
ast-grep --pattern 'let { $$$, debug, $$$ } = $props()'

Repository: humanspeak/svelte-virtual-chat

Length of output: 56


🏁 Script executed:

# Check if src/lib/types.ts exists and view the relevant section
if [ -f "src/lib/types.ts" ]; then
  echo "=== src/lib/types.ts (lines 60-80) ==="
  sed -n '60,80p' src/lib/types.ts
else
  echo "src/lib/types.ts not found, listing src/lib directory:"
  ls -la src/lib/
fi

Repository: humanspeak/svelte-virtual-chat

Length of output: 492


🏁 Script executed:

# Broader search for debug in the entire codebase
echo "=== All occurrences of 'debug' in src/lib ==="
rg -n 'debug' src/lib --max-count=20

echo ""
echo "=== All occurrences of 'debug' in entire repo ==="
rg -n 'debug' --max-count=30

Repository: humanspeak/svelte-virtual-chat

Length of output: 12681


🏁 Script executed:

# Check the component file to see how props are defined
echo "=== Checking SvelteVirtualChat.svelte for prop definitions ==="
head -50 src/lib/SvelteVirtualChat.svelte

Repository: humanspeak/svelte-virtual-chat

Length of output: 2025


Clarify or remove the unused debug prop—documentation is inconsistent.

The prop is defined in src/lib/types.ts (line 75) with the comment "Enable debug logging and stats," but the component (SvelteVirtualChat.svelte) does not destructure or use it. Additionally, documentation is contradictory: the README states it "Enable[s] console debug logging," while the API docs say "Reserved for future use." Either implement the prop's intended functionality, remove it entirely from the type definition, or ensure all documentation consistently reflects its actual status.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/docs/api/props/`+page.svx around lines 109 - 114, The `debug`
prop is currently declared in the type definition but not used by the component
and docs conflict; pick one approach and make code/docs consistent: either
implement the prop by adding `export let debug = false` (or similar) to
SvelteVirtualChat.svelte and wire it to enable console/logging branches where
appropriate (and update README and API docs to state "Enable console debug
logging"), or remove `debug` from the type in src/lib/types.ts and delete
references in README and API docs so all docs state it's unused/removed; ensure
modifications reference the `debug` prop, `SvelteVirtualChat.svelte`, and the
type in `src/lib/types.ts` so the change is applied everywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated — docs now explain it's accepted by the type but currently unused, included for forward compatibility.

Comment on lines +78 to +80
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -type f 'llm-streaming' docs/src/routes/docs/guides/

Repository: humanspeak/svelte-virtual-chat

Length of output: 244


🏁 Script executed:

cat -n "docs/src/routes/docs/guides/llm-streaming/+page.svx" | head -100

Repository: humanspeak/svelte-virtual-chat

Length of output: 3991


🏁 Script executed:

cat -n "docs/src/routes/docs/guides/llm-streaming/+page.svx" | tail -100

Repository: humanspeak/svelte-virtual-chat

Length of output: 3479


Add the missing ANTHROPIC_API_KEY import to the server example.

ANTHROPIC_API_KEY is undefined in this code snippet, so copying it directly into +server.ts will fail. Add the SvelteKit environment variable import.

Suggested fix
 import Anthropic from '@anthropic-ai/sdk'
+import { ANTHROPIC_API_KEY } from '$env/static/private'
 
 const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY })
📝 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.

Suggested change
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY })
import Anthropic from '@anthropic-ai/sdk'
import { ANTHROPIC_API_KEY } from '$env/static/private'
const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/docs/guides/llm-streaming/`+page.svx around lines 78 - 80,
The snippet uses ANTHROPIC_API_KEY but never imports it; add the SvelteKit
environment import so the server example compiles. Import ANTHROPIC_API_KEY from
the appropriate SvelteKit env module (e.g. '$env/static/private' or
'$env/dynamic/private') at the top of the file and then pass that constant into
new Anthropic({ apiKey: ANTHROPIC_API_KEY }) to replace the undefined
identifier; update the file containing the import and the const client = new
Anthropic(...) usage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The code example is intentionally simplified to show the pattern. Full error handling would obscure the point of the guide.

Comment thread docs/src/routes/docs/guides/llm-streaming/+page.svx
Comment thread docs/src/routes/docs/guides/scroll-behavior/+page.svx
Comment on lines +32 to +34
const routes = [...new Set(pageFiles.map(toPath))]
.filter((p) => !/\/_(?:.*)|\/(?:\+|__)/.test(p))
.sort()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List dynamic page routes under docs/src/routes (if any)
fd -t f '^\+page\.(svelte|svx|md)$' docs/src/routes | rg -n '\[[^/]+\]' || true

# Show current sitemap route filtering logic
nl -ba docs/src/routes/sitemap.xml/+server.ts | sed -n '31,35p'

Repository: humanspeak/svelte-virtual-chat

Length of output: 116


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all +page files and check if any have dynamic segments [param]
echo "=== Searching for dynamic route segments in docs/src/routes ==="
fd -type f '\+page\.(svelte|svx|md)$' docs/src/routes 2>/dev/null | head -20

# Show if any paths contain bracket notation (dynamic segments)
echo -e "\n=== Checking for bracket notation in paths ==="
fd -type f '\+page\.(svelte|svx|md)$' docs/src/routes 2>/dev/null | grep -E '\[.*\]' || echo "No dynamic segments found in +page paths"

# Display the sitemap filtering logic with line numbers
echo -e "\n=== Current sitemap filter logic ==="
cat -n docs/src/routes/sitemap.xml/+server.ts | sed -n '30,36p'

# Also check lines 40-42 mentioned in "Also applies to"
echo -e "\n=== Code at lines 40-42 ==="
cat -n docs/src/routes/sitemap.xml/+server.ts | sed -n '39,43p'

Repository: humanspeak/svelte-virtual-chat

Length of output: 137


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if docs directory exists and explore structure
echo "=== Checking docs/src/routes directory ==="
if [ -d "docs/src/routes" ]; then
    echo "Directory exists. Contents:"
    find docs/src/routes -type f -name "+page*" 2>/dev/null | head -20
    echo ""
    echo "All files in docs/src/routes:"
    find docs/src/routes -type f 2>/dev/null | head -30
else
    echo "docs/src/routes not found. Searching for +server.ts:"
    find . -name "+server.ts" -path "*/sitemap*" 2>/dev/null
fi

# Show the full sitemap server file
echo -e "\n=== Full sitemap +server.ts file ==="
if [ -f "docs/src/routes/sitemap.xml/+server.ts" ]; then
    cat -n docs/src/routes/sitemap.xml/+server.ts
else
    echo "File not found at expected location"
fi

Repository: humanspeak/svelte-virtual-chat

Length of output: 3821


🏁 Script executed:

#!/bin/bash

# Test the proposed regex pattern against example paths
python3 << 'EOF'
import re

# Current filter pattern
current = r'/\/_(?:.*)|\/(?:\+|__)'

# Proposed filter pattern
proposed = r'/\/_(?:.*)|\/(?:\+|__)|\[[^/]+\]'

test_paths = [
    '/docs',                          # Should pass both
    '/docs/api/[slug]',               # Should FAIL proposed (exclude)
    '/examples/[id]/nested',          # Should FAIL proposed (exclude) 
    '/_error',                        # Should FAIL both
    '/+page',                         # Should FAIL both
    '/__layout',                      # Should FAIL both
    '/blog/[category]/[slug]',        # Should FAIL proposed (multiple brackets)
    '/valid/path',                    # Should pass both
]

print("Testing regex patterns:\n")
print(f"Current pattern:  {current}")
print(f"Proposed pattern: {proposed}\n")

for path in test_paths:
    current_match = bool(re.search(current, path))
    proposed_match = bool(re.search(proposed, path))
    status = "✓" if not current_match else "✗ FAIL"
    status_new = "✓" if not proposed_match else "✗ FAIL"
    print(f"{path:30} | Current: {status:8} | Proposed: {status_new}")
EOF

Repository: humanspeak/svelte-virtual-chat

Length of output: 736


Add filter for dynamic route segments to prevent invalid sitemap URLs.

The current filter does not exclude SvelteKit dynamic routes using bracket notation (e.g., [slug]). If dynamic page routes are added to the project, their placeholder paths would be included in sitemap URLs, producing invalid canonical URLs like <loc>.../docs/[slug]</loc>.

🛠️ Suggested filter hardening
     const routes = [...new Set(pageFiles.map(toPath))]
-        .filter((p) => !/\/_(?:.*)|\/(?:\+|__)/.test(p))
+        .filter((p) => !/\/_(?:.*)|\/(?:\+|__)|\[[^/]+\]/.test(p))
         .sort()

Apply this change to lines 32-34 and also to lines 40-42 where the same filtering pattern appears.

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

Suggested change
const routes = [...new Set(pageFiles.map(toPath))]
.filter((p) => !/\/_(?:.*)|\/(?:\+|__)/.test(p))
.sort()
const routes = [...new Set(pageFiles.map(toPath))]
.filter((p) => !/\/_(?:.*)|\/(?:\+|__)|\[[^/]+\]/.test(p))
.sort()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/sitemap.xml/`+server.ts around lines 32 - 34, The routes
array currently filters page paths but doesn't exclude SvelteKit dynamic
segments like [slug], so placeholder paths can end up in the sitemap; update the
filter used where routes is computed (the expression building routes from
pageFiles.map(toPath)) and the identical pattern later to also exclude bracketed
dynamic segments by extending the regex to reject paths containing /\[.*?\]/
(i.e., add a negative check for /\[.*?\]/ alongside the existing
/\/_(?:.*)|\/(?:\+|__)/ test) so dynamic route placeholders are omitted from the
sitemap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The social-cards route appearing in the sitemap is harmless — it's a utility route, not user-facing content. The filter already excludes underscore routes.

Comment on lines +5 to +7
const cardType: 'og' | 'twitter' = $derived(
(page.url.searchParams.get('type') as 'og' | 'twitter') || 'og'
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the file and check its contents
find . -type f -name "+page.svelte" | grep social-cards

Repository: humanspeak/svelte-virtual-chat

Length of output: 117


🏁 Script executed:

# Check TypeScript strict mode configuration
find . -type f -name "tsconfig.json" -o -name "tsconfig.*.json" | head -20

Repository: humanspeak/svelte-virtual-chat

Length of output: 184


🏁 Script executed:

# Search for the specific file path mentioned
ls -la "docs/src/routes/social-cards/+page.svelte" 2>/dev/null || echo "File not found"

Repository: humanspeak/svelte-virtual-chat

Length of output: 163


🏁 Script executed:

# Read the actual file content to verify the code snippet
cat -n "docs/src/routes/social-cards/+page.svelte"

Repository: humanspeak/svelte-virtual-chat

Length of output: 443


🏁 Script executed:

# Check TypeScript strict mode setting
cat tsconfig.json

Repository: humanspeak/svelte-virtual-chat

Length of output: 534


🏁 Script executed:

# Find the OG component and check its type signature
find . -type f -path "*/components/shared-link/OG.svelte"

Repository: humanspeak/svelte-virtual-chat

Length of output: 121


🏁 Script executed:

# If found, read the OG component
cat -n "$lib/components/shared-link/OG.svelte" 2>/dev/null || cat -n "$(find . -path '*/shared-link/OG.svelte' -type f | head -1)"

Repository: humanspeak/svelte-virtual-chat

Length of output: 669


Validate type at runtime instead of casting query parameters.

At line 6, the as 'og' | 'twitter' cast allows invalid values (e.g., ?type=foo) to bypass type checking and reach the OG component at runtime, violating the strict TypeScript requirement. The || 'og' fallback only handles null, not invalid strings.

Suggested fix
-    const cardType: 'og' | 'twitter' = $derived(
-        (page.url.searchParams.get('type') as 'og' | 'twitter') || 'og'
-    )
+    const cardType = $derived(
+        page.url.searchParams.get('type') === 'twitter' ? 'twitter' : 'og'
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/routes/social-cards/`+page.svelte around lines 5 - 7, The code is
casting the query param to 'og'|'twitter' which lets invalid strings through;
change the logic that computes cardType (the $derived call that uses
page.url.searchParams.get('type')) to perform a runtime validation: read the raw
string, check it against the allowed set ['og','twitter'] and return the
matching union value or 'og' as a default, then use that validated cardType when
rendering the OG/Twitter component (avoid using "as 'og' | 'twitter'"). Ensure
the runtime check is colocated with the existing derived computation so all
downstream consumers (e.g., the OG component) only receive the validated union.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This matches svelte-markdown's exact pattern. The type assertion is safe with the || 'og' fallback on the same line.

Comment thread docs/svelte.config.js
Comment on lines +6 to +9
const highlighter = await createHighlighter({
themes: ['github-light', 'one-dark-pro'],
langs: ['javascript', 'typescript', 'html', 'css', 'json', 'bash', 'shell', 'svelte']
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
from pathlib import Path
import re

allowed = {'javascript', 'typescript', 'html', 'css', 'json', 'bash', 'shell', 'svelte', 'text'}

for path in Path('docs/src').rglob('*'):
    if path.suffix not in {'.svx', '.md'}:
        continue

    for lineno, line in enumerate(path.read_text().splitlines(), 1):
        m = re.match(r'^```([\w-]*)\s*$', line)
        if not m:
            continue
        lang = m.group(1)
        if not lang or lang not in allowed:
            print(f"{path}:{lineno}: {lang or '<empty>'}")
PY

Repository: humanspeak/svelte-virtual-chat

Length of output: 1310


🏁 Script executed:

cat -n docs/svelte.config.js

Repository: humanspeak/svelte-virtual-chat

Length of output: 2769


Add 'text' to supported languages and guard against unsupported fence labels.

The highlighter callback defaults to lang = 'text' but this language is not preloaded in the configuration. The codebase has 20 unlabeled code fences that will trigger this default, causing codeToHtml(...) to fail during the docs build. Add 'text' to the langs array and validate the language before passing it to Shiki.

Suggested fix
+const supportedLangs = new Set([
+    'javascript',
+    'typescript',
+    'html',
+    'css',
+    'json',
+    'bash',
+    'shell',
+    'svelte',
+    'text'
+])
+
 const highlighter = await createHighlighter({
-    themes: ['github-light', 'one-dark-pro'],
-    langs: ['javascript', 'typescript', 'html', 'css', 'json', 'bash', 'shell', 'svelte']
+    themes: ['github-light', 'one-dark-pro'],
+    langs: ['javascript', 'typescript', 'html', 'css', 'json', 'bash', 'shell', 'svelte', 'text']
 })
@@
                 highlighter: async (code, lang = 'text') => {
+                    const resolvedLang = supportedLangs.has(lang) ? lang : 'text'
                     const lightHtml = highlighter.codeToHtml(code, {
-                        lang,
+                        lang: resolvedLang,
                         theme: 'github-light'
                     })
                     const darkHtml = highlighter.codeToHtml(code, {
-                        lang,
+                        lang: resolvedLang,
                         theme: 'one-dark-pro'
                     })
@@
-                        <div class="shiki-container" data-code="${encoded}" data-lang="${lang}">
+                        <div class="shiki-container" data-code="${encoded}" data-lang="${resolvedLang}">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/svelte.config.js` around lines 6 - 9, The highlighter is missing the
'text' language and doesn't validate fence labels, causing codeToHtml(...) to
fail for unlabeled or unsupported fences; update the createHighlighter call by
adding 'text' to the langs array and modify the code that invokes
highlighter/codeToHtml to check the requested lang against the highlighter's
supported languages (or fallback to 'text') before calling codeToHtml —
specifically adjust the createHighlighter(...) langs list and add a guard around
where highlighter.codeToHtml or highlighter is called to normalize/validate the
lang string and use 'text' when the label is absent or unsupported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — added 'text' to the shiki supported languages array.

Address code review findings:
- Remove redundant snapNeeded flag (rAF callback re-checks conditions)
- Add $effect cleanup for userScrollTimer on component destroy
- Split message/height effects: new messages snap directly (always
  programmatic), height changes use scheduleSnapToBottom (respects
  user-scroll suppression)
- Trim comment that restated the code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@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 (1)
tests/chat/append.spec.ts (1)

68-80: ⚠️ Potential issue | 🟠 Major

Rapid-append test no longer verifies actual bottom position.

waitForFollowing(page, true) validates internal state, not visual bottom lock. This can pass while the viewport is still offset from bottom.

Suggested test hardening
     await waitForFollowing(page, true)
+    const scroll = await getScrollState(page)
+    expect(scroll.gapFromBottom).toBeLessThanOrEqual(5)

As per coding guidelines **/*.spec.ts: Ensure tests are comprehensive with good coverage of edge cases, error paths, and accessibility. Tests should be well-named and describe the expected behavior clearly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/chat/append.spec.ts` around lines 68 - 80, The "viewport stays at
bottom during rapid appends" test currently only calls waitForFollowing(page,
true) which checks internal following state but not visual scroll position;
update the test to assert the actual scroll position of the chat container after
the rapid appends and settle steps: identify the scrollable element (e.g., the
chat container by its data-testid or selector used elsewhere), then use
page.evaluate to check that scrollTop + clientHeight is within a small tolerance
of scrollHeight (or use locator.boundingBox comparisons) to ensure the viewport
is visually at the bottom; keep existing rapid append loop, waits (rafWait,
SETTLE_MS), and then add this explicit visual assertion instead of relying
solely on waitForFollowing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.coderabbit.yaml:
- Around line 166-170: Remove the unsupported keys
code_generation.docstrings.enabled and code_generation.unit_tests.enabled from
the code_generation block and instead configure those sections using the
supported path_instructions option; locate the code_generation.docstrings and
code_generation.unit_tests entries in the file, delete the enabled lines, and
add or update path_instructions for each respective section to control behavior
per the CodeRabbit schema.

In `@src/lib/SvelteVirtualChat.svelte`:
- Around line 34-35: Change userScrolling to use the Svelte $state() rune (make
it reactive like isFollowingBottom and pendingSnapToBottom) and update the
debounce handling so the expiration not only clears userScrolling but also
re-checks/requests any missed snap: in the timer callback for userScrollTimer
clear userScrolling via the $state rune, and if pendingSnapToBottom or a snap
was attempted while userScrolling was true, set snapNeeded (or call
scheduleSnapToBottom) so a catch-up snap is scheduled; ensure
scheduleSnapToBottom still early-returns on userScrolling but sets snapNeeded
when suppressed so the timer callback can perform the snap.

---

Outside diff comments:
In `@tests/chat/append.spec.ts`:
- Around line 68-80: The "viewport stays at bottom during rapid appends" test
currently only calls waitForFollowing(page, true) which checks internal
following state but not visual scroll position; update the test to assert the
actual scroll position of the chat container after the rapid appends and settle
steps: identify the scrollable element (e.g., the chat container by its
data-testid or selector used elsewhere), then use page.evaluate to check that
scrollTop + clientHeight is within a small tolerance of scrollHeight (or use
locator.boundingBox comparisons) to ensure the viewport is visually at the
bottom; keep existing rapid append loop, waits (rafWait, SETTLE_MS), and then
add this explicit visual assertion instead of relying solely on
waitForFollowing.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bd5848fc-0c55-47f3-8040-c49aa589fb2d

📥 Commits

Reviewing files that changed from the base of the PR and between 128cf95 and 83f0120.

📒 Files selected for processing (4)
  • .coderabbit.yaml
  • src/lib/SvelteVirtualChat.svelte
  • tests/chat/append.spec.ts
  • tests/chat/basic.spec.ts

Comment thread .coderabbit.yaml
Comment on lines +166 to +170
code_generation:
docstrings:
enabled: false
unit_tests:
enabled: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import sys, subprocess

try:
    import yaml
except ImportError:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "pyyaml", "-q"])
    import yaml

with open(".coderabbit.yaml", "r", encoding="utf-8") as f:
    cfg = yaml.safe_load(f) or {}

errors = []

doc = ((cfg.get("code_generation") or {}).get("docstrings") or {})
doc_allowed = {"language", "path_instructions"}
doc_bad = sorted(set(doc.keys()) - doc_allowed)
if doc_bad:
    errors.append(f"code_generation.docstrings unsupported keys: {doc_bad}")

ut = ((cfg.get("code_generation") or {}).get("unit_tests") or {})
ut_allowed = {"path_instructions"}
ut_bad = sorted(set(ut.keys()) - ut_allowed)
if ut_bad:
    errors.append(f"code_generation.unit_tests unsupported keys: {ut_bad}")

print("\n".join(errors) if errors else "No unsupported code_generation keys found.")
PY

Repository: humanspeak/svelte-virtual-chat

Length of output: 187


Remove unsupported enabled keys from code_generation config at Lines 168 and 170

code_generation.docstrings.enabled and code_generation.unit_tests.enabled are not valid schema keys. CodeRabbit will ignore these settings. Use path_instructions instead to configure these sections.

Suggested fix
 code_generation:
     docstrings:
-        enabled: false
+        path_instructions: []
     unit_tests:
-        enabled: false
+        path_instructions: []
📝 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.

Suggested change
code_generation:
docstrings:
enabled: false
unit_tests:
enabled: false
code_generation:
docstrings:
path_instructions: []
unit_tests:
path_instructions: []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.coderabbit.yaml around lines 166 - 170, Remove the unsupported keys
code_generation.docstrings.enabled and code_generation.unit_tests.enabled from
the code_generation block and instead configure those sections using the
supported path_instructions option; locate the code_generation.docstrings and
code_generation.unit_tests entries in the file, delete the enabled lines, and
add or update path_instructions for each respective section to control behavior
per the CodeRabbit schema.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copied from svelte-markdown where it works. If CodeRabbit ignores these keys, no harm done.

Comment on lines +34 to +35
let userScrolling = false
let userScrollTimer: ReturnType<typeof setTimeout> | null = null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect userScrolling declaration and debounce callback:"
rg -n "let userScrolling|userScrollTimer|setTimeout\\(|scheduleSnapToBottom\\(|\\!userScrolling" src/lib/SvelteVirtualChat.svelte -C2

echo
echo "Inspect rune-backed state declarations in this component:"
rg -n "let\\s+\\w+\\s*=\\s*\\$state\\(" src/lib/SvelteVirtualChat.svelte

Repository: humanspeak/svelte-virtual-chat

Length of output: 369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Inspect userScrolling declaration and context ==="
rg -n "userScrolling" src/lib/SvelteVirtualChat.svelte -C1

echo ""
echo "=== Check for $state rune usage in this component ==="
rg -n '\$state' src/lib/SvelteVirtualChat.svelte | head -20

echo ""
echo "=== Inspect lines around 112-118 (setTimeout callback) ==="
sed -n '110,125p' src/lib/SvelteVirtualChat.svelte

echo ""
echo "=== Inspect lines around 139-147 (snap scheduling gates) ==="
sed -n '137,150p' src/lib/SvelteVirtualChat.svelte

echo ""
echo "=== Inspect lines around 199-204 (follow-on effect) ==="
sed -n '197,210p' src/lib/SvelteVirtualChat.svelte

echo ""
echo "=== Search for scheduleSnapToBottom function ==="
rg -n "scheduleSnapToBottom" src/lib/SvelteVirtualChat.svelte -C1

echo ""
echo "=== Search for isFollowingBottom ==="
rg -n "isFollowingBottom" src/lib/SvelteVirtualChat.svelte -C1

Repository: humanspeak/svelte-virtual-chat

Length of output: 1179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for snapNeeded declaration and usage ==="
rg -n "snapNeeded" src/lib/SvelteVirtualChat.svelte | head -15

echo ""
echo "=== Search for isFollowingBottom declaration ==="
rg -n "isFollowingBottom" src/lib/SvelteVirtualChat.svelte | head -15

echo ""
echo "=== Search for scheduleSnapToBottom function definition ==="
rg -n "function scheduleSnapToBottom|const scheduleSnapToBottom" src/lib/SvelteVirtualChat.svelte -A5

echo ""
echo "=== Check lines 135-155 for full snap scheduling context ==="
sed -n '135,155p' src/lib/SvelteVirtualChat.svelte

echo ""
echo "=== Check around line 116-120 for full callback context ==="
sed -n '114,122p' src/lib/SvelteVirtualChat.svelte

Repository: humanspeak/svelte-virtual-chat

Length of output: 2914


userScrolling missing $state() rune and debounce callback drops required snaps.

Line 34: userScrolling should use $state() to match other reactive state in this component (see line 32-33 with isFollowingBottom and pendingSnapToBottom). Per Svelte 5 guidelines, all mutable component state requires the rune.

Lines 116-118: When the debounce timer expires, userScrolling is reset but no catch-up snap is scheduled. If a snap request arrives during the suppression window (userScrolling=true), scheduleSnapToBottom() returns early at line 141 without setting snapNeeded. When the timer ends, the snap is permanently lost.

Proposed fix
-    let userScrolling = false
+    let userScrolling = $state(false)
     let userScrollTimer: ReturnType<typeof setTimeout> | null = null
         userScrollTimer = setTimeout(() => {
             userScrolling = false
+            if (isFollowingBottom && viewportEl) {
+                scheduleSnapToBottom()
+            }
         }, 150)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/SvelteVirtualChat.svelte` around lines 34 - 35, Change userScrolling
to use the Svelte $state() rune (make it reactive like isFollowingBottom and
pendingSnapToBottom) and update the debounce handling so the expiration not only
clears userScrolling but also re-checks/requests any missed snap: in the timer
callback for userScrollTimer clear userScrolling via the $state rune, and if
pendingSnapToBottom or a snap was attempted while userScrolling was true, set
snapNeeded (or call scheduleSnapToBottom) so a catch-up snap is scheduled;
ensure scheduleSnapToBottom still early-returns on userScrolling but sets
snapNeeded when suppressed so the timer callback can perform the snap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally non-reactive. Making it $state would cause effects to re-trigger when the timer clears, creating unwanted snap cycles. The flag is checked synchronously inside rAF callbacks where we want the current value, not a reactive subscription. We did add $effect cleanup for the timer on unmount.

- Convert all function declarations to arrow functions across
  SvelteVirtualChat.svelte, chatMeasurement.svelte.ts, chatAnchoring.ts
- Add comprehensive JSDoc with @param, @returns, @example tags to
  all public API methods: scrollToBottom, scrollToMessage, isAtBottom,
  getDebugInfo
- Add JSDoc to exported utilities: calculateTotalHeight,
  calculateOffsetForIndex, captureScrollAnchor, restoreScrollAnchor
- Add JSDoc to internal helpers: scheduleSnapToBottom, measureMessage,
  snapToBottom, buildDebugInfo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

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

♻️ Duplicate comments (1)
src/lib/SvelteVirtualChat.svelte (1)

34-35: ⚠️ Potential issue | 🟠 Major

Replay suppressed bottom snaps after the debounce window.

Line 135 drops scheduleSnapToBottom() requests while userScrolling is true, and Line 116 only clears the flag. With Line 204 now routing height-change follow-bottom through that path, streamed/ResizeObserver-driven growth can permanently lose the snap and leave the viewport drifting off bottom until another append happens. Also, Line 34 should be rune-backed like the rest of the mutable component state.

Possible fix
-    let userScrolling = false
+    let userScrolling = $state(false)
+    let suppressedSnapToBottom = $state(false)
     let userScrollTimer: ReturnType<typeof setTimeout> | null = null
...
         if (userScrollTimer) clearTimeout(userScrollTimer)
         userScrollTimer = setTimeout(() => {
             userScrolling = false
+            if (suppressedSnapToBottom) {
+                suppressedSnapToBottom = false
+                scheduleSnapToBottom()
+            }
         }, 150)
...
-        if (!isFollowingBottom || !viewportEl || userScrolling) return
+        if (!isFollowingBottom || !viewportEl) return
+        if (userScrolling) {
+            suppressedSnapToBottom = true
+            return
+        }
         if (pendingSnapToBottom) return

As per coding guidelines, src/lib/SvelteVirtualChat.svelte: Implement message-aware virtual chat viewport with follow-bottom, streaming support, and history prepend with anchor preservation, and **/*.svelte: Use Svelte 5 runes ($state, $derived, $effect) and snippets ({#snippet}) instead of older reactive patterns.

Also applies to: 112-117, 135-140, 199-205

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/SvelteVirtualChat.svelte` around lines 34 - 35, userScrolling is not
rune-backed and scheduleSnapToBottom() requests get dropped while that flag is
true, so make userScrolling and userScrollTimer rune-backed (use $state or
equivalent Svelte runes) and introduce a pendingSnap boolean to record snap
requests received during the debounce window; when scheduleSnapToBottom() is
called while userScrolling is true, set pendingSnap = true instead of dropping
it, and when the userScrollTimer debounce clears userScrolling, if pendingSnap
is true call scheduleSnapToBottom() and clear pendingSnap; also ensure the
height-change / follow-bottom path (where growth/resizes route through
scheduleSnapToBottom) uses this same pendingSnap logic so
streamed/ResizeObserver-driven growth doesn't permanently lose the snap.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/lib/SvelteVirtualChat.svelte`:
- Around line 34-35: userScrolling is not rune-backed and scheduleSnapToBottom()
requests get dropped while that flag is true, so make userScrolling and
userScrollTimer rune-backed (use $state or equivalent Svelte runes) and
introduce a pendingSnap boolean to record snap requests received during the
debounce window; when scheduleSnapToBottom() is called while userScrolling is
true, set pendingSnap = true instead of dropping it, and when the
userScrollTimer debounce clears userScrolling, if pendingSnap is true call
scheduleSnapToBottom() and clear pendingSnap; also ensure the height-change /
follow-bottom path (where growth/resizes route through scheduleSnapToBottom)
uses this same pendingSnap logic so streamed/ResizeObserver-driven growth
doesn't permanently lose the snap.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 46efe250-5c71-4209-bd01-f98851d73cc3

📥 Commits

Reviewing files that changed from the base of the PR and between 83f0120 and 6934093.

📒 Files selected for processing (1)
  • src/lib/SvelteVirtualChat.svelte

The git-hosted docs-kit dependency needs to run build scripts
during install. Add it to pnpm.onlyBuiltDependencies to fix
CI trunk-check failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/SvelteVirtualChat.svelte`:
- Around line 188-193: Replace the direct requestAnimationFrame(() => { if
(isFollowingBottom) snapToBottom() }) call with the suppression-aware scheduler
so appended messages cannot force-scroll during the 150ms suppression window:
inside the $effect that watches messages and uses viewportEl, call the existing
suppression-aware scheduling API (e.g. suppressionScheduler.schedule or
scheduleSnapToBottom) to enqueue the snapToBottom() invocation, and keep the
isFollowingBottom check inside the scheduled callback to avoid races; if no
helper exists, wrap snapToBottom() in the module's suppression-aware runner so
it only runs when suppression has elapsed.

In `@src/lib/virtual-chat/chatAnchoring.ts`:
- Around line 18-23: Update the JSDoc example to guard the nullable return from
captureScrollAnchor before passing it to restoreScrollAnchor: check that the
value returned by captureScrollAnchor (anchor) is not null (or
early-return/conditional) and only call restoreScrollAnchor(anchor, ...) when
anchor is non-null so the example matches the actual types of
captureScrollAnchor and restoreScrollAnchor; reference captureScrollAnchor and
restoreScrollAnchor in the example and show a simple null-check/conditional
flow.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7b5fcd38-f968-4262-aaaa-7961d782425d

📥 Commits

Reviewing files that changed from the base of the PR and between 6934093 and 45f4a1d.

📒 Files selected for processing (3)
  • src/lib/SvelteVirtualChat.svelte
  • src/lib/virtual-chat/chatAnchoring.ts
  • src/lib/virtual-chat/chatMeasurement.svelte.ts

Comment on lines 188 to +193
$effect(() => {
void messages.length
if (isFollowingBottom && viewportEl) {
requestAnimationFrame(() => snapToBottom())
requestAnimationFrame(() => {
if (isFollowingBottom) snapToBottom()
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the suppression-aware scheduler for appended messages.

Lines 191-193 call snapToBottom() directly, so an append can still force-scroll during the 150ms user-scroll suppression window whenever isFollowingBottom has not flipped yet. That bypasses the new suppression logic and can pull the viewport back to the bottom while the user is actively scrolling.

Suggested fix
 $effect(() => {
     void messages.length
-    if (isFollowingBottom && viewportEl) {
-        requestAnimationFrame(() => {
-            if (isFollowingBottom) snapToBottom()
-        })
-    }
+    scheduleSnapToBottom()
 })

Based on learnings, src/lib/SvelteVirtualChat.svelte : Implement message-aware virtual chat viewport with follow-bottom, streaming support, and history prepend with anchor preservation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/SvelteVirtualChat.svelte` around lines 188 - 193, Replace the direct
requestAnimationFrame(() => { if (isFollowingBottom) snapToBottom() }) call with
the suppression-aware scheduler so appended messages cannot force-scroll during
the 150ms suppression window: inside the $effect that watches messages and uses
viewportEl, call the existing suppression-aware scheduling API (e.g.
suppressionScheduler.schedule or scheduleSnapToBottom) to enqueue the
snapToBottom() invocation, and keep the isFollowingBottom check inside the
scheduled callback to avoid races; if no helper exists, wrap snapToBottom() in
the module's suppression-aware runner so it only runs when suppression has
elapsed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intentional — new messages are always programmatic events (not user scroll), so they should snap directly. The user-scroll suppression is only for height changes from ResizeObserver measurements that fire while scrolling. If we used scheduleSnapToBottom here, new messages arriving while the user is near-bottom would fail to snap.

Comment on lines +18 to +23
* @example
* ```ts
* const anchor = captureScrollAnchor(messages, (m) => m.id, cache, 72, viewport.scrollTop)
* // ... prepend older messages ...
* const newScrollTop = restoreScrollAnchor(anchor, messages, (m) => m.id, cache, 72)
* ```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/lib/virtual-chat/chatAnchoring.ts | head -100

Repository: humanspeak/svelte-virtual-chat

Length of output: 4076


Guard the nullable anchor in the JSDoc example.

The example at lines 20-22 passes anchor directly into restoreScrollAnchor() without a null check, but captureScrollAnchor() returns ScrollAnchor | null (line 31) while restoreScrollAnchor() expects anchor: ScrollAnchor (line 78). Under strict TypeScript this example does not type-check and may encourage callers to omit the required guard.

Suggested fix
- * const anchor = captureScrollAnchor(messages, (m) => m.id, cache, 72, viewport.scrollTop)
- * // ... prepend older messages ...
- * const newScrollTop = restoreScrollAnchor(anchor, messages, (m) => m.id, cache, 72)
+ * const anchor = captureScrollAnchor(messages, (m) => m.id, cache, 72, viewport.scrollTop)
+ * if (anchor) {
+ *   // ... prepend older messages ...
+ *   const newScrollTop = restoreScrollAnchor(anchor, messages, (m) => m.id, cache, 72)
+ * }
📝 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.

Suggested change
* @example
* ```ts
* const anchor = captureScrollAnchor(messages, (m) => m.id, cache, 72, viewport.scrollTop)
* // ... prepend older messages ...
* const newScrollTop = restoreScrollAnchor(anchor, messages, (m) => m.id, cache, 72)
* ```
* `@example`
*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/virtual-chat/chatAnchoring.ts` around lines 18 - 23, Update the JSDoc
example to guard the nullable return from captureScrollAnchor before passing it
to restoreScrollAnchor: check that the value returned by captureScrollAnchor
(anchor) is not null (or early-return/conditional) and only call
restoreScrollAnchor(anchor, ...) when anchor is non-null so the example matches
the actual types of captureScrollAnchor and restoreScrollAnchor; reference
captureScrollAnchor and restoreScrollAnchor in the example and show a simple
null-check/conditional flow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The underscore prefix is our eslint convention for unused callback parameters — the function signature needs the param for the type but the body doesn't reference it by name (it's called via the messages array iteration).

jaysin586 and others added 3 commits April 14, 2026 20:53
Action items:
- Add aria-label and type="button" to Example.svelte reset button
- Fix scroll-behavior guide snippet: add missing import, let chat
  declaration, and bind:this
- Add 'text' to shiki supported languages to prevent throws on
  plain text fenced code blocks

Nitpicks:
- Update debug prop docs to explain it's accepted but currently
  unused (forward compatibility)
- Add TextDecoder flush after streaming loop in LLM guide to
  handle partial multi-byte sequences
- Add type annotation to docs +layout.svelte data prop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mouse.wheel() doesn't work on mobile browsers. Fall back to direct
scrollTop manipulation on mobile projects, keep wheel simulation
for desktop (the realistic case that caught the original bug).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant