feat(docs): complete docs site with docs-kit, dark mode, and full pages#3
Conversation
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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a docs site integration using Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
Found 1 test error on Blacksmith runners: Error
|
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>
There was a problem hiding this comment.
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 | 🟡 MinorConsider cleanup on component unmount to prevent timeout leaks.
The
timeoutIdis cleared instopStreaming(), 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.
childrenis untyped at Line 2, which weakens type safety in alang="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: Addlang="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: ClarifyscrollToMessageoptions parameter structure.The table shows
smoothas 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: Addlang="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:
- Removing one button
- 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$effectfor initial breadcrumb setup.Setting
breadcrumbContext.breadcrumbsdirectly during component initialization (lines 12-14) is a side effect during render. While it works, Svelte 5 prefers side effects in$effectblocks. TheafterNavigatehook 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.pathnamechanges.🤖 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 fortype.The
typeprop 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'fromscript-srcfor 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
⛔ Files ignored due to path filters (4)
docs/src/lib/assets/logo.svgis excluded by!**/*.svgdocs/static/og-default.pngis excluded by!**/*.pngdocs/static/twitter-default.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (44)
docs/.prettierignoredocs/.prettierrcdocs/package.jsondocs/scripts/fetch-github-stats.tsdocs/scripts/generate-social-cards.tsdocs/src/app.cssdocs/src/app.htmldocs/src/hooks.server.tsdocs/src/lib/components/general/Example.sveltedocs/src/lib/components/shared-link/OG.sveltedocs/src/lib/docs-config.tsdocs/src/lib/docsNav.tsdocs/src/lib/examples/BasicChat.sveltedocs/src/lib/examples/HistoryLoading.sveltedocs/src/lib/examples/StreamingChat.sveltedocs/src/lib/icons.tsdocs/src/routes/+layout.sveltedocs/src/routes/+layout.tsdocs/src/routes/+page.sveltedocs/src/routes/docs/+layout.server.tsdocs/src/routes/docs/+layout.sveltedocs/src/routes/docs/api/imperative/+page.svxdocs/src/routes/docs/api/props/+page.svxdocs/src/routes/docs/api/svelte-virtual-chat/+page.svxdocs/src/routes/docs/getting-started/+page.svxdocs/src/routes/docs/guides/history-loading/+page.svxdocs/src/routes/docs/guides/llm-streaming/+page.svxdocs/src/routes/docs/guides/scroll-behavior/+page.svxdocs/src/routes/examples/+layout.sveltedocs/src/routes/examples/+page.sveltedocs/src/routes/examples/basic-chat/+page.sveltedocs/src/routes/examples/history-loading/+page.sveltedocs/src/routes/examples/streaming/+page.sveltedocs/src/routes/sitemap.xml/+server.tsdocs/src/routes/social-cards/+layout.sveltedocs/src/routes/social-cards/+page.sveltedocs/static/llms-full.txtdocs/static/llms.txtdocs/static/robots.txtdocs/svelte.config.jsdocs/tsconfig.jsondocs/vite.config.tsdocs/wrangler.jsoncmprocs.yaml
| @source '../node_modules/@humanspeak/docs-kit/dist'; | ||
| @import '@humanspeak/docs-kit/styles/base.css'; | ||
| @plugin '@tailwindcss/typography'; |
There was a problem hiding this comment.
🧩 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' || trueRepository: 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 fRepository: 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 || trueRepository: 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:
- 1: https://github.com/stylelint-scss/stylelint-config-standard-scss
- 2: https://github.com/stylelint-scss/stylelint-config-standard-scss/tree/v17.0.0
- 3: https://github.com/stylelint-scss/stylelint-config-recommended-scss
- 4: Replace standard
at-rule-no-unknownby the one from stylelint-scss bjankord/stylelint-config-sass-guidelines#18 - 5: at-rule-no-unknown errors stylelint-scss/stylelint-scss#86
🌐 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:
- 1: https://tailwindcss.com/docs/functions-and-directives
- 2: https://tailwindcss.com/blog/tailwindcss-v4
- 3: https://v3.tailwindcss.com/docs/v4-beta
- 4: https://ui.spectrumhq.in/blog/tailwind-css-v4-migration-guide
- 5: https://devtoolbox.dedyn.io/blog/tailwind-css-v4-complete-guide
- 6: Add
@import "…" referencetailwindlabs/tailwindcss#15228
🏁 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".
There was a problem hiding this comment.
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.
| ### `debug` | ||
|
|
||
| - **Type:** `boolean` | ||
| - **Default:** `false` | ||
|
|
||
| Reserved for future use. |
There was a problem hiding this comment.
🧩 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/
fiRepository: 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=30Repository: 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.svelteRepository: 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.
There was a problem hiding this comment.
Updated — docs now explain it's accepted by the type but currently unused, included for forward compatibility.
| import Anthropic from '@anthropic-ai/sdk' | ||
|
|
||
| const client = new Anthropic({ apiKey: ANTHROPIC_API_KEY }) |
There was a problem hiding this comment.
🧩 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 -100Repository: humanspeak/svelte-virtual-chat
Length of output: 3991
🏁 Script executed:
cat -n "docs/src/routes/docs/guides/llm-streaming/+page.svx" | tail -100Repository: 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.
| 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.
There was a problem hiding this comment.
The code example is intentionally simplified to show the pattern. Full error handling would obscure the point of the guide.
| const routes = [...new Set(pageFiles.map(toPath))] | ||
| .filter((p) => !/\/_(?:.*)|\/(?:\+|__)/.test(p)) | ||
| .sort() |
There was a problem hiding this comment.
🧩 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"
fiRepository: 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}")
EOFRepository: 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.
| 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.
There was a problem hiding this comment.
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.
| const cardType: 'og' | 'twitter' = $derived( | ||
| (page.url.searchParams.get('type') as 'og' | 'twitter') || 'og' | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the file and check its contents
find . -type f -name "+page.svelte" | grep social-cardsRepository: 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 -20Repository: 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.jsonRepository: 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.
There was a problem hiding this comment.
This matches svelte-markdown's exact pattern. The type assertion is safe with the || 'og' fallback on the same line.
| const highlighter = await createHighlighter({ | ||
| themes: ['github-light', 'one-dark-pro'], | ||
| langs: ['javascript', 'typescript', 'html', 'css', 'json', 'bash', 'shell', 'svelte'] | ||
| }) |
There was a problem hiding this comment.
🧩 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>'}")
PYRepository: humanspeak/svelte-virtual-chat
Length of output: 1310
🏁 Script executed:
cat -n docs/svelte.config.jsRepository: 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.
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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 | 🟠 MajorRapid-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
📒 Files selected for processing (4)
.coderabbit.yamlsrc/lib/SvelteVirtualChat.sveltetests/chat/append.spec.tstests/chat/basic.spec.ts
| code_generation: | ||
| docstrings: | ||
| enabled: false | ||
| unit_tests: | ||
| enabled: false |
There was a problem hiding this comment.
🧩 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.")
PYRepository: 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.
| 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.
There was a problem hiding this comment.
Copied from svelte-markdown where it works. If CodeRabbit ignores these keys, no harm done.
| let userScrolling = false | ||
| let userScrollTimer: ReturnType<typeof setTimeout> | null = null |
There was a problem hiding this comment.
🧩 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.svelteRepository: 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 -C1Repository: 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.svelteRepository: 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.
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/lib/SvelteVirtualChat.svelte (1)
34-35:⚠️ Potential issue | 🟠 MajorReplay suppressed bottom snaps after the debounce window.
Line 135 drops
scheduleSnapToBottom()requests whileuserScrollingis 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) returnAs 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
📒 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>
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
src/lib/SvelteVirtualChat.sveltesrc/lib/virtual-chat/chatAnchoring.tssrc/lib/virtual-chat/chatMeasurement.svelte.ts
| $effect(() => { | ||
| void messages.length | ||
| if (isFollowingBottom && viewportEl) { | ||
| requestAnimationFrame(() => snapToBottom()) | ||
| requestAnimationFrame(() => { | ||
| if (isFollowingBottom) snapToBottom() | ||
| }) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| * @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) | ||
| * ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/lib/virtual-chat/chatAnchoring.ts | head -100Repository: 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.
| * @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.
There was a problem hiding this comment.
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).
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>
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
@humanspeak/docs-kit(RootLayout, Header, Footer, dark mode, SEO)/social-cardsrouteCommits
128cf95build(docs): bump wrangler compatibility_date to 2026-04-0740279f1fix(docs): convert all examples to dark mode with semantic tokensfa685dedocs: add cross-marketing callout to streaming examplefd4aa0cfix(docs): close all deltas with svelte-markdown docs site859b4d4feat(docs): add dynamic sitemap.xml routedca416efeat(docs): add full docs pages, OG generation, live conversation demoe6ceb73feat(docs): rebuild docs site with @humanspeak/docs-kit