refactor: introduce parameter structs for repository methods, add CI/CD workflows, and adopt Rust 1.94 let-chaining#4
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds dependency and workflow automation, refactors backend repository and service APIs around typed request structs, and updates dashboard and web app components, routes, styles, and configs. ChangesCI/CD and dependency automation
Backend typed-input refactor
Dashboard and web cleanup
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/audit.yml:
- Around line 16-19: The audit job workflow has write-capable token permissions
(issues: write and checks: write) but the actions/checkout step at line 26
persists Git credentials by default, which stores the GITHUB_TOKEN in git config
and increases token exposure risk. Modify the actions/checkout step to include
the persist-credentials property set to false to prevent the GITHUB_TOKEN from
being stored in git config for subsequent workflow steps.
In @.github/workflows/release.yml:
- Around line 11-13: The checkout action in this workflow job persists
credentials by default, which creates a security risk given the `contents:
write` permission. Locate the checkout action step (around Line 35) and add the
`persist-credentials: false` parameter to disable credential persistence and
reduce token exposure during the build steps.
- Around line 21-33: The workflow matrix defines different target architectures
(x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, aarch64-apple-darwin) but the
build step at line 63 running `bunx nx run backend:build` does not pass the
target to Cargo, causing it to compile for the host architecture instead.
Additionally, the artifact copy path at line 69 uses a hardcoded
`target/release/` directory instead of the target-specific path. To fix this,
you need to pass the matrix.target value to the Cargo build command (via the
backend build configuration or build step environment variable) and update the
artifact copy path to use the target-specific directory structure (e.g.,
`target/${{ matrix.target }}/release/` instead of the hardcoded path) so that
each platform builds and packages the correct architecture binary.
In `@apps/backend/src/handlers/entry_handler.rs`:
- Around line 56-60: The function returning Result<Arc<dyn StorageProvider>,
StatusCode> is using raw StatusCode directly instead of the custom AppError enum
required by backend error handling standards. Change the return type from
Result<Arc<dyn StorageProvider>, StatusCode> to Result<Arc<dyn StorageProvider>,
AppError>, and replace the ok_or(StatusCode::INTERNAL_SERVER_ERROR) call with
ok_or(...) using an appropriate AppError variant (e.g.,
AppError::InternalServerError or similar). Apply this same fix to the instance
at line 156 as well, ensuring all storage-resolution HTTP failures use AppError
for centralized error mapping.
In `@apps/backend/src/handlers/singleton_handler.rs`:
- Around line 27-30: The function returning storage provider lookup results is
using raw StatusCode directly instead of the custom AppError enum for error
handling. Change the return type from Result<Arc<dyn StorageProvider>,
StatusCode> to Result<Arc<dyn StorageProvider>, AppError>, and replace the
.ok_or(StatusCode::INTERNAL_SERVER_ERROR) call with .ok_or() mapping to an
appropriate AppError variant (such as AppError::InternalServerError or similar).
This applies to both the location at lines 27-30 and the similar pattern
mentioned at line 90 to maintain consistency with the shared AppError contract.
In `@apps/backend/src/test_helpers.rs`:
- Around line 664-675: The update method in the test repository is ignoring the
site_id parameter (destructured with underscore in UpdateEntryParams) and only
matching entries by id, which allows updates across sites unlike the production
repository. Modify the condition in the entries.iter_mut().find() call to check
both that e.id matches the provided id AND that the site_id parameter matches
the entry's site_id to enforce the same site-scoped update constraint as
production.
In `@apps/dashboard/src/components/app-breadcrumb.tsx`:
- Around line 191-193: The breadcrumb keys in the map function for config.crumbs
(around line 192) and the related location at line 212-213 are derived from
label values, which can cause collisions when multiple crumbs resolve to the
same text label. Replace the key construction that currently uses
`crumb-${"label" in def ? def.label : def.labelFrom}` with a guaranteed-unique
identifier. Use the array index `i` as part of the key (e.g., `crumb-${i}`) or
implement a unique ID system for each breadcrumb definition if one exists.
Ensure both occurrences of this pattern are updated consistently to use stable,
non-colliding keys.
In `@apps/dashboard/src/components/backups/backups-section.tsx`:
- Around line 256-266: The checkbox rows are missing semantic `<label>` HTML
elements, which breaks accessibility by making the text non-clickable for
toggling and removing assistive technology context. Wrap each checkbox and its
associated text in a `<label>` element that connects to the checkbox using
proper HTML label semantics (the label should contain the Checkbox component and
the text). Apply this pattern consistently across all checkbox rows including
the ones for includeFiles, encrypt, import as a new site, select all, and each
individual site row to ensure all checkboxes maintain proper semantic structure
and clickability.
In `@apps/dashboard/src/routes/_admin/sites`.$siteId/collections.tsx:
- Around line 481-484: The key prop in the options map function for
field.options uses only the option value (opt), which causes key collisions when
duplicate option values exist. Since the map callback already receives optIdx as
the index parameter, modify the key prop to combine both the option value and
its index to create a unique identifier for each item in the list. This ensures
proper React reconciliation even when options are duplicated.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 67500b8a-8cc0-4274-9994-6e29666f546c
📒 Files selected for processing (65)
.github/dependabot.yml.github/workflows/audit.yml.github/workflows/ci.yml.github/workflows/release.ymlapps/backend/Cargo.tomlapps/backend/src/config.rsapps/backend/src/graphql/mutation/entry.rsapps/backend/src/graphql/query/mod.rsapps/backend/src/grpc/auth.rsapps/backend/src/grpc/server.rsapps/backend/src/grpc/services/entry.rsapps/backend/src/handlers/backup_handler.rsapps/backend/src/handlers/entry_handler.rsapps/backend/src/handlers/file_handler.rsapps/backend/src/handlers/singleton_handler.rsapps/backend/src/mcp/server.rsapps/backend/src/mcp/tools/entry.rsapps/backend/src/middleware/rate_limit.rsapps/backend/src/paths.rsapps/backend/src/repository/mysql/access_token.rsapps/backend/src/repository/mysql/entry.rsapps/backend/src/repository/mysql/file.rsapps/backend/src/repository/mysql/webhook.rsapps/backend/src/repository/postgres/access_token.rsapps/backend/src/repository/postgres/entry.rsapps/backend/src/repository/postgres/file.rsapps/backend/src/repository/postgres/webhook.rsapps/backend/src/repository/sqlite/access_token.rsapps/backend/src/repository/sqlite/entry.rsapps/backend/src/repository/sqlite/file.rsapps/backend/src/repository/sqlite/webhook.rsapps/backend/src/repository/traits.rsapps/backend/src/services/access_token.rsapps/backend/src/services/backup/mod.rsapps/backend/src/services/entry.rsapps/backend/src/services/file.rsapps/backend/src/services/search/schema.rsapps/backend/src/services/webhook.rsapps/backend/src/storage/mod.rsapps/backend/src/test_helpers.rsapps/backend/tests/common/grpc.rsapps/backend/tests/common/server.rsapps/backend/tests/graphql/collections_tests.rsapps/backend/tests/graphql/entries_tests.rsapps/backend/tests/graphql/files_tests.rsapps/backend/tests/graphql/webhooks_tests.rsapps/backend/tests/grpc/files_tests.rsapps/backend/tests/grpc/webhooks_tests.rsapps/backend/tests/mcp_stdio.rsapps/backend/tests/rest/backups_tests.rsapps/backend/tests/rest/collections_tests.rsapps/backend/tests/rest/roles_tests.rsapps/dashboard/src/components/app-breadcrumb.tsxapps/dashboard/src/components/backups/backups-section.tsxapps/dashboard/src/components/dynamic-form.tsxapps/dashboard/src/components/file-picker-dialog.tsxapps/dashboard/src/components/revisions-panel.tsxapps/dashboard/src/components/sidebar/app-sidebar.tsxapps/dashboard/src/components/sidebar/site-switcher.tsxapps/dashboard/src/components/site-settings/user-combobox.tsxapps/dashboard/src/lib/api.tsapps/dashboard/src/routes/_admin.tsxapps/dashboard/src/routes/_admin/sites.$siteId/collections.tsxdeny.tomlrust-toolchain.toml
💤 Files with no reviewable changes (1)
- apps/dashboard/src/components/site-settings/user-combobox.tsx
3 steps in ci.yml omitted toolchain input. The action requires it when pinned to a SHA; it does not read rust-toolchain.toml. Add toolchain: 1.94 to all three usages (lint, test, test-os).
Deploying cms with
|
| Latest commit: |
8b572fa
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://00d69b63.cms-8f5.pages.dev |
| Branch Preview URL: | https://dev.cms-8f5.pages.dev |
…tter accessibility
…correct keys and labels
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/components/landing/hero-section.tsx (1)
142-147:⚠️ Potential issue | 🟡 MinorAdd
aria-hiddento the decorative background video.Line 142 renders a non-interactive background video element. Without
aria-hidden="true", assistive technology may announce irrelevant media to users. Add the attribute to improve accessibility.Suggested fix
<video + aria-hidden="true" autoPlay muted loop playsInline🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/landing/hero-section.tsx` around lines 142 - 147, The decorative background video element in the hero-section component is missing the aria-hidden attribute, which causes assistive technologies to announce irrelevant media content to users. Add the aria-hidden="true" attribute to the video element (the one with className containing "w-full h-full object-cover object-center opacity-80") to properly hide it from screen readers and other assistive technologies, since this is a decorative background element and not meant to be interacted with or announced to users.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Line 67: The runner label `macos-13` in the matrix entry is not a valid GitHub
Actions runner label and will cause the macOS job to fail. Replace the
`macos-13` value with `macos-15-intel` or another currently available Intel
macOS runner label that GitHub Actions supports to ensure the release workflow
can successfully execute on the macOS runner.
In `@apps/web/src/app/`(home)/_download/page.tsx:
- Around line 317-330: The button elements containing "AGPL v3 License" and
"installation guide" text are non-functional as they lack onClick handlers or
navigation targets. Replace these button elements with anchor tags (a elements)
that link to the appropriate URLs for the AGPL v3 License and the installation
guide respectively. Maintain the existing className styling for consistent
appearance while making these controls functional.
In `@apps/web/src/app/`(home)/home.css:
- Around line 44-47: In the home.css file, fix Stylelint keyword-case violations
by converting keywords to lowercase in CSS value declarations. In the
--font-display CSS custom property declaration that includes Georgia, change
Georgia to lowercase georgia. Additionally, at line 104 where currentColor
appears, change it to lowercase currentcolor. These changes ensure compliance
with the value-keyword-case Stylelint rule which requires keyword values to be
in lowercase.
In `@apps/web/src/components/landing/hero-section.tsx`:
- Around line 62-66: The dependency array in the BlurWord effect (lines 62-66)
is tracking array prototype methods (letters.map, letters.forEach) and
GRADIENT_HOLD, which are insufficient to detect when the word actually changes.
When two words have equal length (like "business" and "platform"), the effect
fails to re-run because GRADIENT_HOLD only changes with word length. Replace the
current dependencies (letters.map, letters.forEach, and GRADIENT_HOLD) with the
actual `word` variable or the `letters` array itself to ensure the effect
re-runs whenever the word changes, regardless of whether its length matches the
previous word.
---
Outside diff comments:
In `@apps/web/src/components/landing/hero-section.tsx`:
- Around line 142-147: The decorative background video element in the
hero-section component is missing the aria-hidden attribute, which causes
assistive technologies to announce irrelevant media content to users. Add the
aria-hidden="true" attribute to the video element (the one with className
containing "w-full h-full object-cover object-center opacity-80") to properly
hide it from screen readers and other assistive technologies, since this is a
decorative background element and not meant to be interacted with or announced
to users.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c14948a-b4ad-4207-997e-1e1e74fd5451
📒 Files selected for processing (70)
.github/workflows/audit.yml.github/workflows/ci.yml.github/workflows/release.ymlapps/backend/src/handlers/entry_handler.rsapps/backend/src/handlers/singleton_handler.rsapps/backend/src/test_helpers.rsapps/dashboard/src/components/app-breadcrumb.tsxapps/dashboard/src/components/backups/backups-section.tsxapps/dashboard/src/components/dashboard-header.tsxapps/dashboard/src/components/dynamic-form.tsxapps/dashboard/src/components/file-picker-dialog.tsxapps/dashboard/src/components/revisions-panel.tsxapps/dashboard/src/components/sidebar/site-switcher.tsxapps/dashboard/src/components/site-avatar.tsxapps/dashboard/src/components/site-settings/members-section.tsxapps/dashboard/src/components/site-settings/user-combobox.tsxapps/dashboard/src/components/tiptap-editor.tsxapps/dashboard/src/contexts/auth-context.tsxapps/dashboard/src/lib/api.tsapps/dashboard/src/routes/_admin/_shell/index.tsxapps/dashboard/src/routes/_admin/_shell/settings.tsxapps/dashboard/src/routes/_admin/sites.$siteId.tsxapps/dashboard/src/routes/_admin/sites.$siteId/collections.tsxapps/dashboard/src/routes/_admin/sites.$siteId/settings.tsxapps/dashboard/src/styles.cssapps/web/biome.jsonapps/web/next.config.mjsapps/web/package.jsonapps/web/postcss.config.mjsapps/web/project.jsonapps/web/source.config.tsapps/web/src/app/(home)/_download/page.tsxapps/web/src/app/(home)/home.cssapps/web/src/app/(home)/layout.tsxapps/web/src/app/(home)/page.tsxapps/web/src/app/api/search/route.tsapps/web/src/app/docs/[[...slug]]/page.tsxapps/web/src/app/docs/docs.cssapps/web/src/app/docs/layout.tsxapps/web/src/app/global.cssapps/web/src/app/layout.tsxapps/web/src/app/llms-full.txt/route.tsapps/web/src/app/llms.mdx/docs/[[...slug]]/route.tsapps/web/src/app/llms.txt/route.tsapps/web/src/app/og/docs/[...slug]/route.tsxapps/web/src/components/landing/ascii-scene.tsxapps/web/src/components/landing/backup-section.tsxapps/web/src/components/landing/cta-section.tsxapps/web/src/components/landing/dashboard-section.tsxapps/web/src/components/landing/database-section.tsxapps/web/src/components/landing/features-section.tsxapps/web/src/components/landing/footer-section.tsxapps/web/src/components/landing/hero-section.tsxapps/web/src/components/landing/how-it-works-section.tsxapps/web/src/components/landing/integrations-section.tsxapps/web/src/components/landing/navigation.tsxapps/web/src/components/landing/security-section.tsxapps/web/src/components/landing/testimonials-section.tsxapps/web/src/components/landing/use-cases.tsxapps/web/src/components/mdx.tsxapps/web/src/components/provider.tsxapps/web/src/components/search.tsxapps/web/src/components/ui/button.tsxapps/web/src/components/ui/select.tsxapps/web/src/lib/cn.tsapps/web/src/lib/layout.shared.tsxapps/web/src/lib/shared.tsapps/web/src/lib/source.tsapps/web/src/lib/utils.tsapps/web/tsconfig.json
✅ Files skipped from review due to trivial changes (39)
- apps/web/src/app/global.css
- apps/web/src/lib/cn.ts
- apps/web/next.config.mjs
- apps/web/src/app/docs/layout.tsx
- apps/web/src/app/layout.tsx
- apps/web/src/app/llms-full.txt/route.ts
- apps/web/src/lib/utils.ts
- apps/dashboard/src/components/site-settings/members-section.tsx
- apps/web/postcss.config.mjs
- apps/dashboard/src/routes/_admin/sites.$siteId.tsx
- apps/dashboard/src/components/tiptap-editor.tsx
- apps/dashboard/src/contexts/auth-context.tsx
- apps/web/src/app/api/search/route.ts
- apps/web/source.config.ts
- apps/dashboard/src/routes/_admin/_shell/index.tsx
- apps/web/src/app/(home)/layout.tsx
- apps/web/src/components/mdx.tsx
- apps/web/src/lib/layout.shared.tsx
- apps/web/src/components/landing/navigation.tsx
- apps/dashboard/src/routes/_admin/_shell/settings.tsx
- apps/web/src/app/llms.txt/route.ts
- apps/web/src/app/(home)/page.tsx
- apps/web/src/components/search.tsx
- apps/dashboard/src/components/dashboard-header.tsx
- apps/dashboard/src/routes/_admin/sites.$siteId/settings.tsx
- apps/web/project.json
- apps/web/src/lib/shared.ts
- apps/web/tsconfig.json
- apps/web/src/components/provider.tsx
- apps/web/src/components/ui/button.tsx
- apps/web/src/app/llms.mdx/docs/[[...slug]]/route.ts
- apps/web/src/app/og/docs/[...slug]/route.tsx
- apps/web/src/lib/source.ts
- apps/dashboard/src/components/site-settings/user-combobox.tsx
- apps/web/src/components/landing/ascii-scene.tsx
- apps/dashboard/src/components/site-avatar.tsx
- apps/dashboard/src/styles.css
- apps/web/package.json
- apps/web/src/components/ui/select.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- .github/workflows/audit.yml
- apps/dashboard/src/routes/_admin/sites.$siteId/collections.tsx
- apps/dashboard/src/components/revisions-panel.tsx
- apps/dashboard/src/components/sidebar/site-switcher.tsx
- apps/dashboard/src/lib/api.ts
- apps/dashboard/src/components/app-breadcrumb.tsx
- .github/workflows/ci.yml
- apps/dashboard/src/components/file-picker-dialog.tsx
- apps/dashboard/src/components/dynamic-form.tsx
| <button | ||
| type="button" | ||
| className="text-foreground hover:underline inline" | ||
| > | ||
| AGPL v3 License | ||
| </a> | ||
| </button> | ||
| . All downloads are verified with SHA256 checksums. Need | ||
| help? Check our{" "} | ||
| <a href="#" className="text-foreground hover:underline"> | ||
| <button | ||
| type="button" | ||
| className="text-foreground hover:underline inline" | ||
| > | ||
| installation guide | ||
| </a> | ||
| </button> |
There was a problem hiding this comment.
Dead legal/help controls with no action
Line 317 and Line 325 now render button elements that have no onClick, so users can focus/click them but nothing happens. Use real links (preferred) or wire explicit click handlers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/`(home)/_download/page.tsx around lines 317 - 330, The
button elements containing "AGPL v3 License" and "installation guide" text are
non-functional as they lack onClick handlers or navigation targets. Replace
these button elements with anchor tags (a elements) that link to the appropriate
URLs for the AGPL v3 License and the installation guide respectively. Maintain
the existing className styling for consistent appearance while making these
controls functional.
| --font-sans: var(--font-instrument), "Instrument Sans", system-ui, sans-serif; | ||
| --font-mono: var(--font-jetbrains), "JetBrains Mono", monospace; | ||
| --font-display: | ||
| var(--font-instrument-serif), "Instrument Serif", Georgia, serif; |
There was a problem hiding this comment.
Fix Stylelint keyword-case violations in font and stroke declarations.
Georgia and currentColor violate value-keyword-case in the current lint config.
Suggested patch
- --font-display:
- var(--font-instrument-serif), "Instrument Serif", Georgia, serif;
+ --font-display:
+ var(--font-instrument-serif), "Instrument Serif", "Georgia", serif;
@@
- -webkit-text-stroke: 1.5px currentColor;
+ -webkit-text-stroke: 1.5px currentcolor;Also applies to: 104-104
🧰 Tools
🪛 Stylelint (17.13.0)
[error] 47-47: Expected "Georgia" to be "georgia" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/`(home)/home.css around lines 44 - 47, In the home.css file,
fix Stylelint keyword-case violations by converting keywords to lowercase in CSS
value declarations. In the --font-display CSS custom property declaration that
includes Georgia, change Georgia to lowercase georgia. Additionally, at line 104
where currentColor appears, change it to lowercase currentcolor. These changes
ensure compliance with the value-keyword-case Stylelint rule which requires
keyword values to be in lowercase.
Source: Linters/SAST tools
| }, [ | ||
| letters.map, // stagger each letter | ||
| letters.forEach, | ||
| GRADIENT_HOLD, | ||
| ]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/web/src/components/landing/hero-section.tsx | head -100Repository: velopulent/cms
Length of output: 3974
🏁 Script executed:
sed -n '50,75p' apps/web/src/components/landing/hero-section.tsxRepository: velopulent/cms
Length of output: 700
🏁 Script executed:
# Search for the BlurWord component definition
rg -n "BlurWord|letters\.map|letters\.forEach" apps/web/src/components/landing/hero-section.tsx -A 3 -B 3Repository: velopulent/cms
Length of output: 1771
Fix BlurWord effect dependencies to track word changes.
Lines 62-66 currently depend on letters.map, letters.forEach, and GRADIENT_HOLD. Since these are array prototype methods (same reference across renders) and GRADIENT_HOLD only changes when word length changes, transitions between equal-length words (e.g., "business" → "platform", both 8 characters) skip the effect entirely, leaving stale animation state.
Suggested fix
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [
- letters.map, // stagger each letter
- letters.forEach,
- GRADIENT_HOLD,
- ]);
+ }, [word]);📝 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.
| }, [ | |
| letters.map, // stagger each letter | |
| letters.forEach, | |
| GRADIENT_HOLD, | |
| ]); | |
| }, [word]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/landing/hero-section.tsx` around lines 62 - 66, The
dependency array in the BlurWord effect (lines 62-66) is tracking array
prototype methods (letters.map, letters.forEach) and GRADIENT_HOLD, which are
insufficient to detect when the word actually changes. When two words have equal
length (like "business" and "platform"), the effect fails to re-run because
GRADIENT_HOLD only changes with word length. Replace the current dependencies
(letters.map, letters.forEach, and GRADIENT_HOLD) with the actual `word`
variable or the `letters` array itself to ensure the effect re-runs whenever the
word changes, regardless of whether its length matches the previous word.
Summary by CodeRabbit
Release Notes
Chores
Improvements