Improve search engine discoverability and add /build developer page#6
Conversation
…page - Fix per-page canonical tags (was hardcoded to root URL for all pages) - Add <link rel="alternate"> tags for machine-readable docs globally - Rewrite robots.txt with explicit named bot directives (GPTBot, PerplexityBot, OAI-SearchBot, Google-Extended, and 8 more crawlers) - Add SoftwareApplication JSON-LD schema to homepage alongside FAQPage - Fix download page schema: operatingSystem as array, downloadUrl array with both TestFlight and APK links, add codeRepository and license - Add CollectionPage + ItemList schema to blog index with XSS-safe JSON serialization matching the pattern used on blog post pages - Add WebPage + ItemList schema to contribute page - Add canonical tags to all pages (download, contribute, privacy-matters, blog index, individual blog posts) - Create /build developer page with Marmot Protocol documentation: MDK quick-start, crate structure, Nostr event kinds, ciphersuite, identity model, client flow, MIP status, known limitations, TypeScript guide, key dependencies, and repository table - Add "Build" link to header nav (desktop + mobile) and footer - Add /build to XML sitemap - Add vercel.json with X-Robots-Tag and Link response headers - Update machine-readable docs index with /build page references and promote "Building with Marmot" section
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds a Build docs page and nav links; injects JSON‑LD and canonical/alternate links across many pages; introduces LLM discovery files and expanded robots allowances; updates sitemap and global Link/X-Robots-Tag headers; adds markdown rendering, caching, tests, CI and related config changes. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 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. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (6)
src/routes/download/+page.svelte (2)
17-20: Consider adding Zapstore URL to downloadUrl array.The page displays three download options (TestFlight, Zapstore, GitHub APK), but the
downloadUrlarray only includes two. Adding the Zapstore URL would provide complete structured data for search engines.♻️ Add Zapstore URL
"downloadUrl": [ "https://testflight.apple.com/join/c6Z7PpxC", + "https://zapstore.dev/apps/naddr1qq2k7un89ecxzunjv4ejuamgd96x2mn0d9ek2q3qwhtn0s68y3cs98zysa4nxrfzss5g5snhndv35tk5m2sudsr7ltmsxpqqqplqk7t8ewh", "https://github.com/marmot-protocol/whitenoise/releases/latest" ],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/download/`+page.svelte around lines 17 - 20, Add the missing Zapstore link to the structured downloadUrl array so all three visible download options are represented; locate the downloadUrl array in +page.svelte (the "downloadUrl" JSON key) and insert the Zapstore URL string alongside the existing TestFlight and GitHub APK entries, ensuring the array remains valid JSON/JS syntax and is exported/used unchanged by any surrounding code.
32-32: Add XSS-safe JSON escaping for consistency.Similar to
contribute/+page.svelte, this file should use.replace(/</g, '\\u003c')for consistent escaping across all JSON-LD injections, matching the pattern used inblog/+page.svelte.♻️ Proposed fix
- {`@html` '<script type="application/ld+json">' + downloadSchema + '</script>'} + {`@html` '<script type="application/ld+json">' + downloadSchema.replace(/</g, '\\u003c') + '</script>'}Note: Since
downloadSchemais already stringified at line 4, the.replace()can be applied directly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/download/`+page.svelte at line 32, The injected JSON-LD is not XSS-escaped: update the HTML injection that uses downloadSchema (the {`@html` '<script type="application/ld+json">' + downloadSchema + '</script>'} expression) to apply the same safe-escaping used elsewhere by calling .replace(/</g, '\\u003c') on the already-stringified downloadSchema before concatenation so the script tag content is escaped consistently.src/routes/contribute/+page.svelte (1)
56-56: Add XSS-safe JSON escaping for consistency.Other pages in this PR (e.g.,
blog/+page.svelteline 43) escape<characters in JSON-LD output using.replace(/</g, '\\u003c'). While the schema here is static and not currently vulnerable, applying consistent escaping prevents future issues if the schema becomes dynamic.♻️ Proposed fix
- {`@html` '<script type="application/ld+json">' + JSON.stringify(contributeSchema) + '</script>'} + {`@html` '<script type="application/ld+json">' + JSON.stringify(contributeSchema).replace(/</g, '\\u003c') + '</script>'}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/contribute/`+page.svelte at line 56, The JSON-LD injection uses {`@html` '<script type="application/ld+json">' + JSON.stringify(contributeSchema) + '</script>'} without escaping, so make it XSS-safe by replacing `<` in the serialized JSON (e.g., call JSON.stringify(contributeSchema).replace(/</g, '\\u003c')) before embedding; update the template that renders contributeSchema to use the escaped string so the output matches other pages' escaping.src/routes/blog/[naddr]/+page.svelte (1)
8-8: Unused reactive state variable.
sanitizedContentis declared with$state("")but is never assigned or used in the template. The actual content rendering usessafeHtml(line 113). This appears to be dead code from a previous implementation.🧹 Remove unused variable
-let sanitizedContent = $state("");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/blog/`[naddr]/+page.svelte at line 8, Remove the dead reactive state by deleting the unused declaration `let sanitizedContent = $state("");` (and any now-unused import of `$state` if present); ensure the template continues to use `safeHtml` for rendering and that no other code references `sanitizedContent` (if any do, replace them to use `safeHtml` or the appropriate value).src/routes/build/+page.svelte (2)
43-43: Avoid raw{@html}for JSON-LD script construction.Line 43 works today, but string-building a
<script>tag via raw HTML is fragile and easier to misuse later. Prefer direct JSON-LD script content with escaped<characters.Proposed safer pattern
- {`@html` '<script type="application/ld+json">' + JSON.stringify(buildSchema) + '</script>'} + <script type="application/ld+json"> + {JSON.stringify(buildSchema).replace(/</g, '\\u003c')} + </script>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/build/`+page.svelte at line 43, Replace the raw {`@html` '<script...'> construction that injects HTML with a real <script type="application/ld+json"> element whose text content is the safely-escaped JSON-LD; specifically stop using the {`@html` ...} injection around the <script> tag and instead render the JSON via JSON.stringify(buildSchema).replace(/</g, '\\u003c') (or an equivalent escape) inside the script element so buildSchema is serialized as text rather than raw HTML.
211-217: Use the projectglitch-*palette consistently on this new page.The new file mixes in
yellow-*andcyan-*classes. Please map these to the definedglitch-*palette for consistency with project theming.As per coding guidelines: "Use custom colors from the glitch-* palette (defined in app.css) for custom color values."
Also applies to: 223-223, 229-229, 235-235, 241-241, 247-247, 283-283, 288-288, 293-293, 315-315, 321-321, 327-327, 333-333, 339-339, 347-347
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/build/`+page.svelte around lines 211 - 217, The rows use utility classes like the span with "bg-yellow-100 text-yellow-800 ..." and other instances of "yellow-*" and "cyan-*" palette classes; replace those with the project's custom palette classes (e.g., "bg-glitch-100 text-glitch-800" or the appropriate glitch-bg/glitch-text tokens defined in app.css) so every occurrence of "bg-yellow-*" "text-yellow-*" and "bg-cyan-*" "text-cyan-*" in the table rows is mapped to the corresponding "glitch-*" classes (match background and text contrast equivalents) to ensure consistent theming across the component (check the span elements and any cells that currently reference yellow/cyan utilities).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/routes/blog/`[naddr]/+page.svelte:
- Line 8: Remove the dead reactive state by deleting the unused declaration `let
sanitizedContent = $state("");` (and any now-unused import of `$state` if
present); ensure the template continues to use `safeHtml` for rendering and that
no other code references `sanitizedContent` (if any do, replace them to use
`safeHtml` or the appropriate value).
In `@src/routes/build/`+page.svelte:
- Line 43: Replace the raw {`@html` '<script...'> construction that injects HTML
with a real <script type="application/ld+json"> element whose text content is
the safely-escaped JSON-LD; specifically stop using the {`@html` ...} injection
around the <script> tag and instead render the JSON via
JSON.stringify(buildSchema).replace(/</g, '\\u003c') (or an equivalent escape)
inside the script element so buildSchema is serialized as text rather than raw
HTML.
- Around line 211-217: The rows use utility classes like the span with
"bg-yellow-100 text-yellow-800 ..." and other instances of "yellow-*" and
"cyan-*" palette classes; replace those with the project's custom palette
classes (e.g., "bg-glitch-100 text-glitch-800" or the appropriate
glitch-bg/glitch-text tokens defined in app.css) so every occurrence of
"bg-yellow-*" "text-yellow-*" and "bg-cyan-*" "text-cyan-*" in the table rows is
mapped to the corresponding "glitch-*" classes (match background and text
contrast equivalents) to ensure consistent theming across the component (check
the span elements and any cells that currently reference yellow/cyan utilities).
In `@src/routes/contribute/`+page.svelte:
- Line 56: The JSON-LD injection uses {`@html` '<script
type="application/ld+json">' + JSON.stringify(contributeSchema) + '</script>'}
without escaping, so make it XSS-safe by replacing `<` in the serialized JSON
(e.g., call JSON.stringify(contributeSchema).replace(/</g, '\\u003c')) before
embedding; update the template that renders contributeSchema to use the escaped
string so the output matches other pages' escaping.
In `@src/routes/download/`+page.svelte:
- Around line 17-20: Add the missing Zapstore link to the structured downloadUrl
array so all three visible download options are represented; locate the
downloadUrl array in +page.svelte (the "downloadUrl" JSON key) and insert the
Zapstore URL string alongside the existing TestFlight and GitHub APK entries,
ensuring the array remains valid JSON/JS syntax and is exported/used unchanged
by any surrounding code.
- Line 32: The injected JSON-LD is not XSS-escaped: update the HTML injection
that uses downloadSchema (the {`@html` '<script type="application/ld+json">' +
downloadSchema + '</script>'} expression) to apply the same safe-escaping used
elsewhere by calling .replace(/</g, '\\u003c') on the already-stringified
downloadSchema before concatenation so the script tag content is escaped
consistently.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
src/app.htmlsrc/lib/components/Footer.sveltesrc/lib/components/Header.sveltesrc/routes/+page.sveltesrc/routes/blog/+page.sveltesrc/routes/blog/[naddr]/+page.sveltesrc/routes/build/+page.sveltesrc/routes/contribute/+page.sveltesrc/routes/download/+page.sveltesrc/routes/privacy-matters/+page.sveltesrc/routes/sitemap.xml/+server.tsstatic/llms.txtstatic/robots.txtvercel.json
- Add XSS-safe JSON escaping (.replace(/</g, '\u003c')) to JSON-LD in download, contribute, and build pages for consistency - Add missing Zapstore URL to downloadUrl structured data array - Remove unused sanitizedContent $state variable from blog post page - Replace yellow-* status badge colors with glitch-* palette on build page
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/routes/build/+page.svelte (1)
247-247: Switch link colors fromtext-cyan-*totext-glitch-*to match project palette rules.These links currently use the non-project palette in several places.
🎨 Suggested patch
- <a href="https://github.com/marmot-protocol/marmot" class="text-cyan-600 hover:underline" target="_blank" rel="noopener noreferrer"> + <a href="https://github.com/marmot-protocol/marmot" class="text-glitch-700 hover:underline" target="_blank" rel="noopener noreferrer"> - <td class="py-3"><a href="https://github.com/openmls/openmls" class="text-cyan-600 hover:underline" target="_blank" rel="noopener noreferrer">github.com/openmls/openmls</a></td> + <td class="py-3"><a href="https://github.com/openmls/openmls" class="text-glitch-700 hover:underline" target="_blank" rel="noopener noreferrer">github.com/openmls/openmls</a></td> - <p class="text-glitch-700 mt-4">All repositories: <a href="https://github.com/marmot-protocol" class="text-cyan-600 hover:underline" target="_blank" rel="noopener noreferrer">github.com/marmot-protocol</a></p> + <p class="text-glitch-700 mt-4">All repositories: <a href="https://github.com/marmot-protocol" class="text-glitch-700 hover:underline" target="_blank" rel="noopener noreferrer">github.com/marmot-protocol</a></p>As per coding guidelines, "Use custom colors from the glitch-* palette (defined in app.css) for custom color values".
Also applies to: 283-283, 288-288, 293-293, 315-315, 321-321, 327-327, 333-333, 339-339, 347-347
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/build/`+page.svelte at line 247, The anchor element linking to github.com/marmot-protocol/marmot (and the other similar anchor tags noted) uses the non-project color class "text-cyan-*" — update those class names to the project palette "text-glitch-*" (e.g., change text-cyan-600 to text-glitch-600), leaving other classes like hover:underline, target, and rel intact; apply the same replacement for all occurrences referenced in the file (the <a> elements at the listed lines) so links use the glitch-* palette.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/routes/build/`+page.svelte:
- Line 247: The anchor element linking to github.com/marmot-protocol/marmot (and
the other similar anchor tags noted) uses the non-project color class
"text-cyan-*" — update those class names to the project palette "text-glitch-*"
(e.g., change text-cyan-600 to text-glitch-600), leaving other classes like
hover:underline, target, and rel intact; apply the same replacement for all
occurrences referenced in the file (the <a> elements at the listed lines) so
links use the glitch-* palette.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/routes/blog/[naddr]/+page.sveltesrc/routes/build/+page.sveltesrc/routes/contribute/+page.sveltesrc/routes/download/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
- src/routes/contribute/+page.svelte
There was a problem hiding this comment.
🧹 Nitpick comments (5)
src/lib/server/blog-markdown.test.ts (1)
22-29: Consider adding a test fordata:URL scheme.While
javascript:URLs are tested,data:URLs in links could also be an attack vector. The currentallowedSchemesconfig should block them, but explicit test coverage would confirm this.💡 Additional test case
it("strips data urls from links", () => { const html = renderBlogHtml("[bad](data:text/html,<script>alert(1)</script>)"); expect(html).not.toContain("data:"); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/server/blog-markdown.test.ts` around lines 22 - 29, Add a test asserting that data: URLs are stripped by the markdown renderer: add a new test alongside the existing "strips javascript urls from links" that calls renderBlogHtml with a markdown link using a data: URL (e.g. "[bad](data:text/html,<script>...)") and assert the resulting HTML does not contain "data:" and that allowed links still render; this verifies the allowedSchemes logic used by renderBlogHtml is blocking data: URLs as intended.src/routes/blog/[naddr]/+page.svelte (1)
4-4: Preferconstfor non-reassigned props destructuring.Since
datais not reassigned within this component,constwould be more idiomatic and clearly signals immutability.♻️ Minor style fix
-let { data }: { data: PageData } = $props(); +const { data }: { data: PageData } = $props();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/blog/`[naddr]/+page.svelte at line 4, The props destructuring uses "let" for data but data is never reassigned; change the declaration "let { data }: { data: PageData } = $props()" to use "const" so the destructured prop is immutable and expresses intent (update the declaration in +page.svelte where { data } is obtained from $props()).src/lib/server/blog-markdown.ts (2)
4-7: Global state: consider local configuration.
marked.setOptions()mutates global state, which could affect other code usingmarkedand complicates testing. Consider using a localMarkedinstance instead.♻️ Suggested refactor using a local instance
-import { marked } from "marked"; +import { Marked } from "marked"; import sanitizeHtml from "sanitize-html"; -marked.setOptions({ +const marked = new Marked({ gfm: true, breaks: true, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/server/blog-markdown.ts` around lines 4 - 7, The code currently calls marked.setOptions(...) which mutates global state; instead instantiate and use a local Marked instance: import or reference the Marked constructor, create const localMarked = new Marked({ gfm: true, breaks: true }) replacing marked.setOptions, and update places that call marked.parse/marked(...) to call localMarked.parse(...) (or localMarked.tokens/parse as used) so configuration stays local to the blog-markdown module and avoids global mutation.
60-63: Explicitasync: falseoption clarifies synchronous behavior.The type assertion works because
marked.parse()defaults toasync: false, which returnsstringsynchronously. Instead of relying on the assertion, pass the option explicitly to make the intent clear:♻️ Refactor suggestion
export function renderBlogHtml(markdown: string): string { - const html = marked.parse(markdown) as string; + const html = marked.parse(markdown, { async: false }); return sanitizeHtml(html, SANITIZE_OPTIONS); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/server/blog-markdown.ts` around lines 60 - 63, The function renderBlogHtml relies on a type assertion because marked.parse() implicitly uses async: false; update the call to marked.parse(markdown, { async: false }) (and remove the "as string" assertion) so the synchronous return is explicit, then pass the resulting string into sanitizeHtml(html, SANITIZE_OPTIONS) as before; this clarifies intent and avoids unsafe casting in renderBlogHtml referencing marked.parse and SANITIZE_OPTIONS.src/routes/+page.svelte (1)
117-125: Use$state()for reactive state in Svelte 5.The
openFaqIndicesvariable is reactive state that changes viatoggleFaq(). Per coding guidelines, Svelte 5 files should declare reactive state using$state()instead of top-levelletdeclarations.♻️ Proposed fix to use Svelte 5 runes
-let openFaqIndices: number[] = []; +let openFaqIndices = $state<number[]>([]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/`+page.svelte around lines 117 - 125, Replace the top-level mutable let openFaqIndices with a Svelte 5 reactive state created by $state() and stop reassigning the variable directly in toggleFaq; declare openFaqIndices using $state([...]) and change toggleFaq to read the current value via the state API and update it via the state's setter/update methods (instead of using includes/filter or spreading to reassign), keeping references to the existing identifiers openFaqIndices and toggleFaq so the rest of the component continues to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/server/blog-markdown.test.ts`:
- Around line 22-29: Add a test asserting that data: URLs are stripped by the
markdown renderer: add a new test alongside the existing "strips javascript urls
from links" that calls renderBlogHtml with a markdown link using a data: URL
(e.g. "[bad](data:text/html,<script>...)") and assert the resulting HTML does
not contain "data:" and that allowed links still render; this verifies the
allowedSchemes logic used by renderBlogHtml is blocking data: URLs as intended.
In `@src/lib/server/blog-markdown.ts`:
- Around line 4-7: The code currently calls marked.setOptions(...) which mutates
global state; instead instantiate and use a local Marked instance: import or
reference the Marked constructor, create const localMarked = new Marked({ gfm:
true, breaks: true }) replacing marked.setOptions, and update places that call
marked.parse/marked(...) to call localMarked.parse(...) (or
localMarked.tokens/parse as used) so configuration stays local to the
blog-markdown module and avoids global mutation.
- Around line 60-63: The function renderBlogHtml relies on a type assertion
because marked.parse() implicitly uses async: false; update the call to
marked.parse(markdown, { async: false }) (and remove the "as string" assertion)
so the synchronous return is explicit, then pass the resulting string into
sanitizeHtml(html, SANITIZE_OPTIONS) as before; this clarifies intent and avoids
unsafe casting in renderBlogHtml referencing marked.parse and SANITIZE_OPTIONS.
In `@src/routes/`+page.svelte:
- Around line 117-125: Replace the top-level mutable let openFaqIndices with a
Svelte 5 reactive state created by $state() and stop reassigning the variable
directly in toggleFaq; declare openFaqIndices using $state([...]) and change
toggleFaq to read the current value via the state API and update it via the
state's setter/update methods (instead of using includes/filter or spreading to
reassign), keeping references to the existing identifiers openFaqIndices and
toggleFaq so the rest of the component continues to work.
In `@src/routes/blog/`[naddr]/+page.svelte:
- Line 4: The props destructuring uses "let" for data but data is never
reassigned; change the declaration "let { data }: { data: PageData } = $props()"
to use "const" so the destructured prop is immutable and expresses intent
(update the declaration in +page.svelte where { data } is obtained from
$props()).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 437acf60-8825-40b5-a4db-ec1c66239d7c
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (23)
.github/workflows/ci.ymlbiome.jsonpackage.jsonsrc/lib/blog-post-cache.test.tssrc/lib/blog-post-cache.tssrc/lib/components/Footer.sveltesrc/lib/nostr.tssrc/lib/server/blog-markdown.test.tssrc/lib/server/blog-markdown.tssrc/routes/+layout.sveltesrc/routes/+page.sveltesrc/routes/blog/+page.sveltesrc/routes/blog/[naddr]/+page.server.tssrc/routes/blog/[naddr]/+page.sveltesrc/routes/blog/[naddr]/page.server.test.tssrc/routes/build/+page.sveltesrc/routes/contribute/+page.sveltesrc/routes/download/+page.sveltesrc/routes/privacy-matters/+page.sveltesrc/routes/sitemap.xml/+server.tssrc/routes/sitemap.xml/server.test.tssvelte.config.jsvercel.json
✅ Files skipped from review due to trivial changes (5)
- biome.json
- .github/workflows/ci.yml
- vercel.json
- src/lib/components/Footer.svelte
- src/routes/build/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (4)
- src/routes/sitemap.xml/+server.ts
- src/routes/blog/+page.svelte
- src/routes/privacy-matters/+page.svelte
- src/routes/download/+page.svelte
Summary
<link rel="alternate">headers pointing to machine-readable documentation filesSoftwareApplicationJSON-LD schema to homepage alongside existingFAQPageoperatingSystemas array,downloadUrlarray with both TestFlight and APK links, pluscodeRepositoryandlicenseCollectionPage+ItemListschema to blog index with XSS-safe serializationWebPage+ItemListschema to contribute page/builddeveloper documentation page with Marmot Protocol reference: MDK quick-start, crate structure, Nostr event kinds table, ciphersuite, identity model, client flow, MIP status, known limitations, TypeScript guide, key dependencies, and repository table/buildto XML sitemapvercel.jsonwithX-Robots-TagandLinkresponse headersllms.txtwith/buildpage references and promoted "Building with Marmot" sectionTest plan
npx svelte-check --threshold errorpasses with 0 errors<head>for correct canonical URL and JSON-LD schemas/buildrenders with all sections (MDK crates, event kinds, MIP status, etc.)/sitemap.xmlincludes/build/robots.txtshows named bot blocks/llms.txtreferences the new/buildpageSummary by CodeRabbit
New Features
Documentation
Chores