Skip to content

Conversation

@AlexSkrypnyk
Copy link
Member

@AlexSkrypnyk AlexSkrypnyk commented Sep 2, 2025

Closes #1951

Summary by CodeRabbit

  • New Features

    • Embedded interactive installer player in docs with playback controls and accompanying installer playback asset.
  • Documentation

    • Installation guide and README updated to surface the interactive installer; release docs now include a step to refresh the installer video; docs tooling and spellcheck updated to support Asciinema.
  • Chores

    • CI workflows and local command added to generate/update installer recordings and related assets.
  • Tests

    • New unit tests and fixtures for the player plus test refinements and formatting updates.

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

Adds tooling and docs to record and embed a Vortex installer asciinema: a script to run/build the installer and produce cast/SVG/PNG assets, GitHub Actions steps to invoke it, an Ahoy command, a React AsciinemaPlayer and tests/fixtures, updated docs pages/assets, and a docs .gitignore entry.

Changes

Cohort / File(s) Summary
CI workflows
.github/workflows/vortex-release-docs.yml, .../vortex-test-installer.yml
Add "Generate video for installer" step (installs asciinema/expect, npm tools and runs .vortex/docs/.utils/update-installer-video.sh); add Node.js setup step in test-installer workflow.
Ahoy tooling
.vortex/.ahoy.yml
Add update-installer-video command and adjust entrypoint invocation to include -e and wrapped bash -e -c "$0" "$@".
Docs utility script
.vortex/docs/.utils/update-installer-video.sh
New script: check deps, build or copy installer PHAR, create Expect driver, record with asciinema, post-process cast (anonymize/remove spawn), convert to SVG and poster PNG, validate and copy final assets into docs.
Docs content (release / install)
.vortex/docs/content/contributing/maintenance/release.mdx, .vortex/docs/content/getting-started/installation.mdx
Add ahoy update-installer-video in release steps; replace static installer PNG with AsciinemaPlayer and update installation copy.
Docs component
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js, .vortex/docs/src/components/AsciinemaPlayer/index.js
Add client-side React AsciinemaPlayer that injects asciinema-player CSS/JS from CDN and instantiates player with configurable props; add barrel export.
Docs assets & misc
.vortex/docs/static/img/installer.json, .vortex/docs/.gitignore, README.md
Add recorded cast JSON asset; ignore static/install; embed installer.svg in README.
Docs tests / fixtures / config
.vortex/docs/tests/fixtures/test-cast.json, .vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js, .vortex/docs/package.json, .vortex/docs/cspell.json, .vortex/docs/.prettierignore
Add test fixture and AsciinemaPlayer unit tests; include tests/ in lint scripts; add "Asciinema" to cspell; add prettierignore entry.
Formatting-only test updates
.vortex/docs/tests/unit/**
Multiple tests reformatted/whitespace-only edits; no semantic changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev/CI
  participant Script as update-installer-video.sh
  participant Tools as (asciinema / expect / php / npx)
  participant Build as Installer build (composer)
  participant Expect as Expect driver
  participant Asci as asciinema
  participant Convert as svg-term / sharp-cli
  participant Docs as Docs assets

  Dev/CI->>Script: invoke
  Script->>Tools: check deps
  alt prebuilt PHAR exists
    Script->>Docs: copy installer.phar
  else
    Script->>Build: build installer PHAR
    Build-->>Script: installer.phar
    Script->>Docs: copy installer.phar
  end
  Script->>Expect: create expect script
  Script->>Asci: record session (spawn php installer via expect)
  Asci-->>Script: cast.json
  Script->>Convert: convert cast -> svg + poster.png
  Convert-->>Script: svg/png
  Script->>Docs: move installer.json, installer.svg, installer.png
  Script-->>Dev/CI: done
Loading
sequenceDiagram
  autonumber
  participant Browser
  participant Page as installation.mdx
  participant Player as AsciinemaPlayer
  participant CDN as CDN (asciinema)
  participant Asset as /img/installer.json + poster

  Browser->>Page: request page
  Page->>Player: hydrate client component
  Player->>CDN: load CSS/JS (if missing)
  Player->>Asset: fetch installer.json + poster
  Asset-->>Player: data
  Player-->>Browser: render interactive playback UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Embed installer asciinema as SVG in docs (#1951) PR produces an SVG from the cast but the docs use a client-side JSON-driven AsciinemaPlayer instead of embedding the inline SVG suggested by the issue.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add React AsciinemaPlayer and switch installation.mdx to JSON player (.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js, .vortex/docs/content/getting-started/installation.mdx) The linked issue requests using SVG embedding (svg-term); replacing the PNG with a JSON-driven player changes the embedding approach rather than directly embedding SVG.
Add Node.js setup step in test workflow (.github/workflows/vortex-test-installer.yml) The linked issue is docs/svg embedding focused; adding Node setup to CI is unrelated to the SVG-embedding objective.

Possibly related PRs

Suggested labels

PR: DO NOT MERGE, PR: Do not review

Poem

In my burrow I press keys and play,
I cast the install in ASCII ray,
I craft an SVG and JSON tune,
Carrots cheer beneath the moon 🌙🥕,
Hooray — the rabbit filmed the install today! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/1951-ascii-cinema

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented Sep 2, 2025

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 77.68%. Comparing base (ffaa5db) to head (4736e98).
⚠️ Report is 4 commits behind head on develop.

Files with missing lines Patch % Lines
.../src/components/AsciinemaPlayer/AsciinemaPlayer.js 97.05% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1963      +/-   ##
===========================================
+ Coverage    77.57%   77.68%   +0.11%     
===========================================
  Files           89       90       +1     
  Lines         5515     5549      +34     
  Branches        35       44       +9     
===========================================
+ Hits          4278     4311      +33     
- Misses        1237     1238       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

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

⚠️ Outside diff range comments (2)
.vortex/docs/content/contributing/maintenance/release.mdx (1)

17-19: Grammar: fix article/phrasing in step 11.

-11. Create a new release notes using [release template](./release_template.md).
+11. Create new release notes using the [release template](./release_template.md).
.vortex/.ahoy.yml (1)

116-122: Entry point arguments are inconsistent (double “-c”, stray “-e”).

As written, Ahoy will invoke: bash -c -e "bash -e -c "$0" "$@" … which is error-prone and nests shells unnecessarily. Use a single Bash with -lc and internal set -e.

 entrypoint:
-  - bash
-  - "-c"
-  - "-e"
-  - bash -e -c "$0" "$@"
-  - '{{cmd}}'
-  - '{{name}}'
+  - bash
+  - -lc
+  - |
+    set -e
+    "$0" "$@"
+  - '{{cmd}}'
+  - '{{name}}'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between be95da9 and 60339c5.

⛔ Files ignored due to path filters (2)
  • .vortex/docs/static/img/installer.png is excluded by !**/*.png
  • .vortex/docs/static/img/installer.svg is excluded by !**/*.svg
📒 Files selected for processing (11)
  • .github/workflows/vortex-release-docs.yml (1 hunks)
  • .github/workflows/vortex-test-installer.yml (2 hunks)
  • .vortex/.ahoy.yml (1 hunks)
  • .vortex/docs/.gitignore (1 hunks)
  • .vortex/docs/.utils/update-installer-video.sh (1 hunks)
  • .vortex/docs/content/contributing/maintenance/release.mdx (1 hunks)
  • .vortex/docs/content/getting-started/installation.mdx (2 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/index.js (1 hunks)
  • .vortex/docs/static/img/installer.json (1 hunks)
  • README.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
README.md

54-54: Inline HTML
Element: img

(MD033, no-inline-html)

🪛 GitHub Check: vortex-test-docs
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js

[failure] 9-9:
Expected { after 'if' condition

🪛 GitHub Actions: Vortex - Test docs
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js

[error] 9-9: ESLint: Expected '{' after 'if' condition. Command: 'eslint src/'

🪛 LanguageTool
.vortex/docs/content/contributing/maintenance/release.mdx

[grammar] ~18-~18: Use articles correctly
Context: ...hoy update-installer-video`. 11. Create a new release notes using [release templa...

(QB_NEW_EN_OTHER_ERROR_IDS_11)


[grammar] ~18-~18: There might be a mistake here.
Context: ...elease template](./release_template.md).

(QB_NEW_EN)

.vortex/docs/content/getting-started/installation.mdx

[grammar] ~5-~5: Use correct spacing
Context: ... '@site/src/components/AsciinemaPlayer'; # Installation The installation process c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 Biome (2.1.2)
.vortex/docs/static/img/installer.json

[error] 1-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-18: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 18-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-29: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 29-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-40: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 40-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-50: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 50-51: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 51-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-65: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 65-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-75: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 75-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-84: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 84-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-89: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 89-91: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 91-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-97: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 97-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-112: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 112-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-116: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 116-120: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 120-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-124: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 124-125: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 125-135: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 135-136: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 136-137: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 137-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 151-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (1)
  • GitHub Check: build (0)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
🔇 Additional comments (5)
.vortex/docs/.utils/update-installer-video.sh (1)

185-190: Expect script uses 'sleep', which isn’t an Expect/Tcl builtin. Use 'after'.

'sleep' will error; 'after' expects milliseconds.

Apply inside the heredoc:

-    sleep $prompt_delay
+    after [expr {int($prompt_delay * 1000)}]

Likely an incorrect or invalid review comment.

README.md (1)

54-55: Replace inline HTML with Markdown image (README.md Lines 54–55)
Asset .vortex/docs/static/img/installer.svg is present.

-<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
+![Vortex installer screenshot](.vortex/docs/static/img/installer.svg)
.vortex/docs/src/components/AsciinemaPlayer/index.js (1)

1-1: LGTM.

Barrel default export is correct.

.vortex/docs/content/getting-started/installation.mdx (1)

27-35: Improve URL handling and accessibility in installation guide

  • Replace the hardcoded src="/img/installer.json" with a base-URL–aware path, e.g.
    src={useBaseUrl('/img/installer.cast')} (or confirm that AsciinemaPlayer already calls useBaseUrl internally).
  • Add an accessible label prop:
    aria-label="Interactive Vortex installer walkthrough".
  • Verify renaming installer.jsoninstaller.cast is intentional and aligns with issue #1951’s SVG vs. interactive-cast scope.
    [vortex/docs/content/getting-started/installation.mdx]
.vortex/docs/static/img/installer.json (1)

1-152: Executed a local scan to confirm:

  • .vortex/docs/static/img/installer.json exists
  • No existing .biomeignore entry for it
  • References in MDX/HTML point at /img/installer.json

This validates the recommendation to either rename the file or add a Biome ignore.

Comment on lines +33 to +37
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.17.1

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Prefer a moving LTS range for Node to reduce churn.

Keeps builds current without manual patch bumps.

-          node-version: 22.17.1
+          node-version: '22.x'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.17.1
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22.x'
🤖 Prompt for AI Agents
.github/workflows/vortex-test-installer.yml around lines 33 to 37: the workflow
pins node-version to a specific patch (22.17.1) which forces frequent manual
updates; change the node-version input to a moving LTS range (for example "22.x"
or "lts/*") so the runner picks current security/patch releases automatically,
update the value in the actions/setup-node step accordingly and keep the rest of
the step unchanged.

Comment on lines 59 to 65
- name: Generate video for installer (not used in the final artifact)
run: |
sudo apt install asciinema expect
./update-installer-video.sh
working-directory: .vortex/docs/.utils

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Same apt reliability fix as release workflow.

Prevent hangs and 404s; add timeout.

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Generate video for installer (not used in the final artifact)
run: |
sudo apt install asciinema expect
./update-installer-video.sh
working-directory: .vortex/docs/.utils
- name: Generate video for installer (not used in the final artifact)
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends asciinema expect
timeout 15m bash ./update-installer-video.sh
working-directory: .vortex/docs/.utils
🤖 Prompt for AI Agents
.github/workflows/vortex-test-installer.yml around lines 59-64: the apt install
step can hang or hit 404s and the script can run forever; make the install
deterministic and add timeouts by running apt-get update first, set
DEBIAN_FRONTEND=noninteractive, use apt-get install -y --no-install-recommends
with Acquire::Retries (e.g. -o Acquire::Retries=3) to reduce transient failures,
and run the update-installer-video.sh under a timeout (e.g. timeout 5m) so the
workflow won't hang indefinitely.

Comment on lines +12 to +14
set -eu
[ "${VORTEX_DEBUG-}" = "1" ] && set -x

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Dangerous default WORKSPACE_DIR + unconditional rm -rf can wipe user data. Use mktemp and guarded cleanup.

Current default points to $HOME/www/demo and is always deleted at exit.

Apply:

-set -eu
+set -euo pipefail
 [ "${VORTEX_DEBUG-}" = "1" ] && set -x
@@
-# Directory for temporary workspace.
-WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"
+# Directory for temporary workspace (set in main; may be overridden via env).
+# WORKSPACE_DIR is intentionally not defaulted here to avoid deleting user dirs.
-  WORKSPACE_DIR="${WORKSPACE_DIR:-"$(mktemp -d -t vortex-installer-recording-XXXXXX)"}"
-  [ ! -d "$WORKSPACE_DIR" ] && mkdir -p "$WORKSPACE_DIR"
+  local CREATED_TMPDIR=""
+  if [[ -z "${WORKSPACE_DIR:-}" ]]; then
+    CREATED_TMPDIR="$(mktemp -d -t vortex-installer-recording-XXXXXX)"
+    WORKSPACE_DIR="$CREATED_TMPDIR"
+  else
+    mkdir -p "$WORKSPACE_DIR"
+  fi
-  cleanup() {
-    info "Cleaning up workspace directory"
-    note "Removing: ${WORKSPACE_DIR}"
-    rm -rf "$WORKSPACE_DIR"
-  }
+  cleanup() {
+    # Only remove if we created it in this run
+    if [[ -n "$CREATED_TMPDIR" && "$WORKSPACE_DIR" == "$CREATED_TMPDIR" ]]; then
+      info "Cleaning up workspace directory"
+      note "Removing: ${WORKSPACE_DIR}"
+      rm -rf "$WORKSPACE_DIR"
+    fi
+  }

Also applies to: 18-18, 376-381, 386-391

🤖 Prompt for AI Agents
In .vortex/docs/.utils/update-installer-video.sh around lines 12-14 (and also
adjust the same pattern at 18, 376-381, 386-391): the script currently defaults
WORKSPACE_DIR to a persistent path ($HOME/www/demo) and unconditionally rm -rf's
it at exit, which can delete user data; change to create a temporary workspace
with mktemp when no WORKSPACE_DIR is provided, track whether the script created
the dir (e.g., a boolean flag), and only perform cleanup (rm -rf) for that temp
directory in the trap; if WORKSPACE_DIR was provided by the user, do not delete
it; also add defensive checks before deletion to ensure the target is not empty
or a root-like path (e.g., ensure it starts with a temp-dir prefix or is under
/tmp) to prevent accidental catastrophic deletes.

sidebar_position: 3
---

import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Ensure client-only rendering to avoid SSR hydration issues.

If AsciinemaPlayer touches window/DOM during render, wrap usage with Docusaurus BrowserOnly or make the component client-only. Otherwise, dev/build may warn or break during SSR.

Example (MDX):

+import BrowserOnly from '@docusaurus/BrowserOnly';
 import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';

And around the player:

-<AsciinemaPlayer ... />
+<BrowserOnly>{() => <AsciinemaPlayer ... />}</BrowserOnly>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
import BrowserOnly from '@docusaurus/BrowserOnly';
import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
{/* …later in your MDX content… */}
<BrowserOnly>
{() => <AsciinemaPlayer /* props here */ />}
</BrowserOnly>
🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: Use correct spacing
Context: ... '@site/src/components/AsciinemaPlayer'; # Installation The installation process c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🤖 Prompt for AI Agents
.vortex/docs/content/getting-started/installation.mdx line 5: the
AsciinemaPlayer is imported at module scope which can cause SSR hydration errors
if it accesses window/DOM; to fix, make the player client-only by either
removing the top-level import and wrapping its usage with Docusaurus BrowserOnly
(import BrowserOnly from '@docusaurus/BrowserOnly' and render the
AsciinemaPlayer inside BrowserOnly) or change AsciinemaPlayer to a client-only
component (lazy/dynamic import or guard for window in its render) so no
DOM/window is accessed during SSR.

Comment on lines 21 to 57
if (!window.AsciinemaPlayer) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
script.onload = () => {
if (containerRef.current && window.AsciinemaPlayer) {
const options = {
autoPlay,
loop,
preload,
controls,
theme
};

// Add poster if provided
if (poster) {
options.poster = poster;
}

// Add startAt if provided (for poster frame selection)
if (startAt !== undefined) {
options.startAt = startAt;
}

window.AsciinemaPlayer.create(src, containerRef.current, options);
}
};
document.head.appendChild(script);
} else {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden dynamic loader: avoid races and surface load errors.

Multiple mounts can inject multiple scripts if the first hasn’t finished loading yet. Also, no onerror handler.

Apply:

-        if (!window.AsciinemaPlayer) {
+        if (!window.AsciinemaPlayer) {
+          // Reuse existing pending script if present
+          const existing = document.querySelector('script[src*="asciinema-player.min.js"]');
+          if (existing) {
+            existing.addEventListener('load', () => {
+              if (containerRef.current && window.AsciinemaPlayer) {
+                const options = { autoPlay, loop, preload, controls, theme, ...(poster ? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) };
+                containerRef.current.innerHTML = '';
+                window.AsciinemaPlayer.create(src, containerRef.current, options);
+              }
+            }, { once: true });
+            return;
+          }
           const script = document.createElement('script');
           script.src = 'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
+          script.async = true;
           script.onload = () => {
             if (containerRef.current && window.AsciinemaPlayer) {
               const options = {
                 autoPlay,
                 loop,
                 preload,
                 controls,
                 theme
               };
               // Add poster if provided
               if (poster) {
                 options.poster = poster;
               }
               // Add startAt if provided (for poster frame selection)
               if (startAt !== undefined) {
                 options.startAt = startAt;
               }
-              window.AsciinemaPlayer.create(src, containerRef.current, options);
+              containerRef.current.innerHTML = '';
+              window.AsciinemaPlayer.create(src, containerRef.current, options);
             }
           };
+          script.onerror = () => console.error('Failed to load Asciinema player script');
           document.head.appendChild(script);
         } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!window.AsciinemaPlayer) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
script.onload = () => {
if (containerRef.current && window.AsciinemaPlayer) {
const options = {
autoPlay,
loop,
preload,
controls,
theme
};
// Add poster if provided
if (poster) {
options.poster = poster;
}
// Add startAt if provided (for poster frame selection)
if (startAt !== undefined) {
options.startAt = startAt;
}
window.AsciinemaPlayer.create(src, containerRef.current, options);
}
};
document.head.appendChild(script);
} else {
if (!window.AsciinemaPlayer) {
// Reuse existing pending script if present
const existing = document.querySelector('script[src*="asciinema-player.min.js"]');
if (existing) {
existing.addEventListener('load', () => {
if (containerRef.current && window.AsciinemaPlayer) {
const options = { autoPlay, loop, preload, controls, theme, ...(poster ? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) };
containerRef.current.innerHTML = '';
window.AsciinemaPlayer.create(src, containerRef.current, options);
}
}, { once: true });
return;
}
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
script.async = true;
script.onload = () => {
if (containerRef.current && window.AsciinemaPlayer) {
const options = {
autoPlay,
loop,
preload,
controls,
theme
};
// Add poster if provided
if (poster) {
options.poster = poster;
}
// Add startAt if provided (for poster frame selection)
if (startAt !== undefined) {
options.startAt = startAt;
}
containerRef.current.innerHTML = '';
window.AsciinemaPlayer.create(src, containerRef.current, options);
}
};
script.onerror = () => console.error('Failed to load Asciinema player script');
document.head.appendChild(script);
} else {
🤖 Prompt for AI Agents
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around lines
21-48: The dynamic loader can race when multiple mounts run before the script
loads and currently has no onerror handling; change the loader to first look for
an existing script element (by the exact src or a unique id) and if found
attach/await its existing load/error handlers (or reuse a loader Promise stored
on window, e.g. window.__asciinemaLoaderPromise) instead of injecting a
duplicate script; when creating a new script set both onload and onerror
handlers (surface error via console.error or reject the loader Promise) and
store the Promise on window so subsequent mounts reuse it, ensuring only one
script is injected and load failures are surfaced to the component.

Comment on lines 26 to 52
const options = {
autoPlay,
loop,
preload,
controls,
theme
};

// Add poster if provided
if (poster) {
options.poster = poster;
}

// Add startAt if provided (for poster frame selection)
if (startAt !== undefined) {
options.startAt = startAt;
}

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Minor: reduce repetition building options.

Build options once to avoid drift.

Apply:

-              const options = {
-                autoPlay,
-                loop,
-                preload,
-                controls,
-                theme
-              };
-              // Add poster if provided
-              if (poster) {
-                options.poster = poster;
-              }
-              // Add startAt if provided (for poster frame selection)
-              if (startAt !== undefined) {
-                options.startAt = startAt;
-              }
+              const options = { autoPlay, loop, preload, controls, theme, ...(poster ? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) };

(and likewise in the already-loaded branch)

Also applies to: 51-68

🤖 Prompt for AI Agents
In .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around lines
26-43 (and similarly update lines 51-68), the options object is built with
repeated conditional assignments (poster, startAt) which risks drift; instead
construct the options once using a single object literal that conditionally
includes poster and startAt (e.g., spread or computed properties) so both
branches use the same concise creation pattern and avoid duplication; apply the
same refactor to the already-loaded branch at lines 51-68.

Comment on lines 44 to 77
window.AsciinemaPlayer.create(src, containerRef.current, options);
}
};
document.head.appendChild(script);
} else {
// AsciinemaPlayer is already loaded
if (containerRef.current && window.AsciinemaPlayer) {
const options = {
autoPlay,
loop,
preload,
controls,
theme
};

// Add poster if provided
if (poster) {
options.poster = poster;
}

// Add startAt if provided (for poster frame selection)
if (startAt !== undefined) {
options.startAt = startAt;
}

window.AsciinemaPlayer.create(src, containerRef.current, options);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent duplicate players and leaks by clearing the container and adding effect cleanup.

Re-creates the player on every prop change without tearing down the previous instance; this can stack DOM nodes. Clear before create and on unmount.

Apply:

-              window.AsciinemaPlayer.create(src, containerRef.current, options);
+              // Clear any previous render before creating a new player
+              containerRef.current.innerHTML = '';
+              window.AsciinemaPlayer.create(src, containerRef.current, options);
-            window.AsciinemaPlayer.create(src, containerRef.current, options);
+            containerRef.current.innerHTML = '';
+            window.AsciinemaPlayer.create(src, containerRef.current, options);
-    loadAsciinemaPlayer();
-  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);
+    loadAsciinemaPlayer();
+    return () => {
+      if (containerRef.current) containerRef.current.innerHTML = '';
+    };
+  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);

Also applies to: 77-79

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1951-ascii-cinema branch from 60339c5 to 1070c96 Compare September 2, 2025 07:56
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (3)
.vortex/docs/content/contributing/maintenance/release.mdx (1)

17-19: Grammar: “Create new release notes”.

-11. Create a new release notes using [release template](./release_template.md).
+11. Create new release notes using [release template](./release_template.md).
.vortex/docs/content/getting-started/installation.mdx (2)

13-15: Fix typo in user-facing docs ("in interactive" → "an interactive").

User-facing grammar issue.

-We automated the first part by providing an in interactive installer. The second
+We automated the first part by providing an interactive installer. The second

97-100: Remove double space before “and”.

Minor readability polish.

-Depending on the services you are using, you will need to set up the
-integrations with your hosting provider, continuous integration provider,  and
+Depending on the services you are using, you will need to set up the
+integrations with your hosting provider, continuous integration provider, and
♻️ Duplicate comments (18)
.vortex/docs/.gitignore (1)

24-25: Clarify intent: is static/install a file or a directory?

Without a trailing slash it matches both; be explicit to avoid surprises.

If it’s a directory:

-static/install
+static/install/

If it’s a single copied file (per the comment), consider a clearer name:

-# Copied installer file.
-static/install
+# Copied installer binary.
+static/installer

Update any scripts accordingly if you rename.

.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (2)

91-93: Add effect cleanup to avoid leaks on unmount.

Clear container to release DOM and listeners.

-    loadAsciinemaPlayer();
-  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);
+    loadAsciinemaPlayer();
+    return () => {
+      if (containerRef.current) containerRef.current.innerHTML = '';
+    };
+  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);

34-61: Dedupe dynamic script injection and add error handling.

Multiple mounts before load can append duplicate scripts; no onerror handler; script not async.

Apply:

-        // Dynamically load the JavaScript
-        if (!window.AsciinemaPlayer) {
-          const script = document.createElement('script');
-          script.src =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
-          script.onload = () => {
-            if (containerRef.current && window.AsciinemaPlayer) {
-              const options = {
-                autoPlay,
-                loop,
-                preload,
-                controls,
-                theme,
-              };
-
-              // Add poster if provided
-              if (poster) {
-                options.poster = poster;
-              }
-
-              // Add startAt if provided (for poster frame selection)
-              if (startAt !== undefined) {
-                options.startAt = startAt;
-              }
-
-              window.AsciinemaPlayer.create(src, containerRef.current, options);
-            }
-          };
-          document.head.appendChild(script);
+        // Dynamically load the JavaScript
+        if (!window.AsciinemaPlayer) {
+          // Reuse a pending script if present to avoid duplicates.
+          const existing = document.getElementById('asciinema-player-cdn');
+          if (existing) {
+            existing.addEventListener('load', () => {
+              if (containerRef.current && window.AsciinemaPlayer) {
+                const options = { autoPlay, loop, preload, controls, theme, ...(poster ? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) };
+                containerRef.current.innerHTML = '';
+                window.AsciinemaPlayer.create(src, containerRef.current, options);
+              }
+            }, { once: true });
+            existing.addEventListener('error', () => console.error('Failed to load Asciinema player script'), { once: true });
+            return;
+          }
+          const script = document.createElement('script');
+          script.id = 'asciinema-player-cdn';
+          script.async = true;
+          script.src = 'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
+          script.onload = () => {
+            if (containerRef.current && window.AsciinemaPlayer) {
+              const options = { autoPlay, loop, preload, controls, theme, ...(poster ? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) };
+              containerRef.current.innerHTML = '';
+              window.AsciinemaPlayer.create(src, containerRef.current, options);
+            }
+          };
+          script.onerror = () => console.error('Failed to load Asciinema player script');
+          document.head.appendChild(script);
.vortex/docs/.utils/update-installer-video.sh (9)

12-18: Harden shell options and remove dangerous default WORKSPACE_DIR.

-set -eu
+set -euo pipefail
@@
-# Directory for temporary workspace.
-WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"
+# Directory for temporary workspace (set in main; may be overridden via env).
+# WORKSPACE_DIR is not defaulted here to avoid deleting user dirs accidentally.

50-69: Add node to deps and provide install hints; keep CI non-interactive.

-  local required_commands=("asciinema" "expect" "php" "composer" "npx")
+  local required_commands=("asciinema" "expect" "php" "composer" "node" "npx")
@@
-    echo "  brew install asciinema expect composer  # macOS" >&2
-    echo "  apt-get install asciinema expect-dev composer  # Ubuntu/Debian" >&2
+    echo "  brew install asciinema expect composer node  # macOS" >&2
+    echo "  apt-get update -y && apt-get install -y --no-install-recommends asciinema expect-dev composer nodejs npm  # Ubuntu/Debian" >&2

76-83: Ensure target dir exists before copying installer.

-    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+    mkdir -p "$(dirname "$INSTALLER_DOCS")"
+    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"

95-110: Make composer commands CI-safe (non-interactive, faster).

-    if ! composer install; then
+    if ! composer install --no-interaction --prefer-dist --no-progress --no-ansi; then
@@
-    if ! composer build; then
+    if ! composer build -n; then

113-120: Ensure target dir exists before copying built installer.

-      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+      mkdir -p "$(dirname "$INSTALLER_DOCS")"
+      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"

286-291: Guard copying installer into workspace.

-  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"
+  if [ ! -f "$INSTALLER_DOCS" ]; then
+    fail "Installer not found at: $INSTALLER_DOCS"
+    return 1
+  fi
+  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"

317-319: Broaden path anonymization and keep sed portable on macOS.

-  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
-  sed "${sed_opts[@]}" -E 's|/Users/[^/]+/|/home/user/|g' "$output_cast"
+  sed_opts=(-i); [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
+  sed "${sed_opts[@]}" -E 's|/(Users|home)/[^/]+/|/home/user/|g' "$output_cast"

323-341: Make all npx calls non-interactive (-y) to avoid CI prompts.

-  if ! npx svg-term --out="$output_svg" <"$output_cast"; then
+  if ! npx -y svg-term --out="$output_svg" <"$output_cast"; then
@@
-  if ! npx svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
+  if ! npx -y svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
@@
-  if ! npx sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then
+  if ! npx -y sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then

376-391: Create temp workspace safely; only delete if we created it.

-  info "Creating temporary workspace"
-  WORKSPACE_DIR="${WORKSPACE_DIR:-"$(mktemp -d -t vortex-installer-recording-XXXXXX)"}"
-  [ ! -d "$WORKSPACE_DIR" ] && mkdir -p "$WORKSPACE_DIR"
+  info "Creating temporary workspace"
+  local CREATED_TMPDIR=""
+  if [[ -z "${WORKSPACE_DIR:-}" ]]; then
+    CREATED_TMPDIR="$(mktemp -d -t vortex-installer-recording-XXXXXX)"
+    WORKSPACE_DIR="$CREATED_TMPDIR"
+  else
+    mkdir -p "$WORKSPACE_DIR"
+  fi
@@
-  cleanup() {
-    info "Cleaning up workspace directory"
-    note "Removing: ${WORKSPACE_DIR}"
-    rm -rf "$WORKSPACE_DIR"
-  }
+  cleanup() {
+    if [[ -n "$CREATED_TMPDIR" && "$WORKSPACE_DIR" == "$CREATED_TMPDIR" && -n "$WORKSPACE_DIR" && "$WORKSPACE_DIR" == /tmp/* ]]; then
+      info "Cleaning up workspace directory"
+      note "Removing: ${WORKSPACE_DIR}"
+      rm -rf "$WORKSPACE_DIR"
+    fi
+  }
.github/workflows/vortex-test-installer.yml (2)

33-37: Prefer moving LTS range for Node to reduce patch churn.

-          node-version: 22.17.1
+          node-version: '22.x'

59-64: Harden apt install and bound script runtime to avoid hangs.

-      - name: Generate video for installer (not used in the final artifact)
-        run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+      - name: Generate video for installer (not used in the final artifact)
+        run: |
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.github/workflows/vortex-release-docs.yml (1)

135-140: Make apt non-interactive and guard the long-running script; optionally upload assets.

Use apt-get with update/-y and --no-install-recommends, set DEBIAN_FRONTEND to avoid prompts, and run the script under a timeout to prevent hangs. Optionally, upload generated assets for debugging.

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
+
+      - name: Upload installer video assets (debug)
+        if: ${{ always() }}
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+        with:
+          name: installer-video-assets
+          path: |
+            .vortex/docs/static/img/installer.json
+            .vortex/docs/static/img/installer.svg
+            .vortex/docs/static/img/installer.png
.vortex/docs/content/getting-started/installation.mdx (2)

5-5: Wrap client-only player with BrowserOnly to avoid SSR hydration issues.

Import BrowserOnly and render the player inside it.

+import BrowserOnly from '@docusaurus/BrowserOnly';
 import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';

27-35: Render player inside BrowserOnly.

-    <AsciinemaPlayer
-      src="/img/installer.json"
-      poster="npt:0:1"
-      autoPlay={false}
-      loop={false}
-      preload={true}
-      controls={true}
-      theme="asciinema"
-    />
+    <BrowserOnly>
+      {() => (
+        <AsciinemaPlayer
+          src="/img/installer.json"
+          poster="npt:0:1"
+          autoPlay={false}
+          loop={false}
+          preload={true}
+          controls={true}
+          theme="asciinema"
+        />
+      )}
+    </BrowserOnly>
.vortex/.ahoy.yml (1)

110-115: Harden the command wrapper and avoid fragile relative paths.

Fail fast, use pipefail, resolve repo root, and run via absolute path.

   update-installer-video:
     usage: Update installer video in the documentation.
     cmd: |
-      cd docs/.utils
-      ./update-installer-video.sh
+      set -euo pipefail
+      REPO_ROOT="$(git rev-parse --show-toplevel)"
+      cd "${REPO_ROOT}/docs/.utils"
+      bash ./update-installer-video.sh
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 60339c5 and 1070c96.

⛔ Files ignored due to path filters (2)
  • .vortex/docs/static/img/installer.png is excluded by !**/*.png
  • .vortex/docs/static/img/installer.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • .github/workflows/vortex-release-docs.yml (1 hunks)
  • .github/workflows/vortex-test-installer.yml (2 hunks)
  • .vortex/.ahoy.yml (1 hunks)
  • .vortex/docs/.gitignore (1 hunks)
  • .vortex/docs/.utils/update-installer-video.sh (1 hunks)
  • .vortex/docs/content/contributing/maintenance/release.mdx (1 hunks)
  • .vortex/docs/content/getting-started/installation.mdx (2 hunks)
  • .vortex/docs/content/tools/drush.mdx (1 hunks)
  • .vortex/docs/content/workflows/deployment.mdx (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/index.js (1 hunks)
  • .vortex/docs/static/img/installer.json (1 hunks)
  • README.md (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.

Applied to files:

  • .vortex/docs/.gitignore
🪛 Biome (2.1.2)
.vortex/docs/static/img/installer.json

[error] 1-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-18: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 18-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-29: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 29-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-40: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 40-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-50: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 50-51: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 51-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-65: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 65-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-75: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 75-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-84: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 84-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-89: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 89-91: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 91-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-97: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 97-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-112: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 112-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-116: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 116-120: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 120-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-124: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 124-125: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 125-135: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 135-136: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 136-137: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 137-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 151-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🪛 LanguageTool
.vortex/docs/content/getting-started/installation.mdx

[grammar] ~5-~5: Use correct spacing
Context: ... '@site/src/components/AsciinemaPlayer'; # Installation The installation process c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

.vortex/docs/content/contributing/maintenance/release.mdx

[grammar] ~18-~18: Use articles correctly
Context: ...hoy update-installer-video`. 11. Create a new release notes using [release templa...

(QB_NEW_EN_OTHER_ERROR_IDS_11)


[grammar] ~18-~18: There might be a mistake here.
Context: ...elease template](./release_template.md).

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
README.md

54-54: Inline HTML
Element: img

(MD033, no-inline-html)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.2)
🔇 Additional comments (5)
.vortex/docs/content/workflows/deployment.mdx (1)

32-32: LGTM: spacing tweak is fine.

Blank line correctly separates the paragraph from the list; no content change.

.vortex/docs/content/tools/drush.mdx (1)

55-55: LGTM: improves Markdown rendering before the code block.

The extra blank line prevents accidental paragraph/code fusion.

README.md (1)

54-55: Replace inline HTML with Markdown image syntax in README.md (lines 54–55)

-<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
+![Vortex installer screenshot](.vortex/docs/static/img/installer.svg)

Asset path .vortex/docs/static/img/installer.svg verified.

.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1)

7-11: No changes required for preload and controls Verified that preload and controls are valid Asciinema Player v3 configuration options.

.vortex/docs/src/components/AsciinemaPlayer/index.js (1)

1-1: LGTM — concise barrel export.

Comment on lines +185 to +190
proc wait_and_enter {} {
global prompt_delay
wait_for_quiet 0.25
sleep \$prompt_delay
safe_send "\r"
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use Expect/Tcl ‘after’ instead of ‘sleep’ for portability.

-    sleep $prompt_delay
+    after [expr {int($prompt_delay * 1000)}]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
proc wait_and_enter {} {
global prompt_delay
wait_for_quiet 0.25
sleep \$prompt_delay
safe_send "\r"
}
proc wait_and_enter {} {
global prompt_delay
wait_for_quiet 0.25
# use Expect/Tcl’s built-in after (ms) instead of sleep for portability
after [expr {int($prompt_delay * 1000)}]
safe_send "\r"
}
🤖 Prompt for AI Agents
In .vortex/docs/.utils/update-installer-video.sh around lines 185 to 190, the
function wait_and_enter uses sleep \$prompt_delay which is not portable in
Expect/Tcl; replace the blocking sleep call with after to schedule the delay in
milliseconds (e.g. after [expr {$prompt_delay * 1000}]) and then perform
safe_send \"\r\" so the script uses Tcl's event loop rather than POSIX sleep;
ensure prompt_delay is treated as seconds (convert to ms) and remove the sleep
line.

Comment on lines 424 to 430
# Copy the processed files.
info "Copying processed files"
note "JSON cast: $temp_cast$CAST_FILE"
cp "$temp_cast" "$CAST_FILE"
note "SVG image: $temp_svg$SVG_FILE"
cp "$temp_svg" "$SVG_FILE"
note "PNG image: $temp_png$PNG_FILE"
cp "$temp_png" "$PNG_FILE"

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure destination dirs exist before copying outputs.

-  cp "$temp_cast" "$CAST_FILE"
+  mkdir -p "$(dirname "$CAST_FILE")"
+  cp "$temp_cast" "$CAST_FILE"
@@
-  cp "$temp_svg" "$SVG_FILE"
+  mkdir -p "$(dirname "$SVG_FILE")"
+  cp "$temp_svg" "$SVG_FILE"
@@
-  cp "$temp_png" "$PNG_FILE"
+  mkdir -p "$(dirname "$PNG_FILE")"
+  cp "$temp_png" "$PNG_FILE"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Copy the processed files.
info "Copying processed files"
note "JSON cast: $temp_cast$CAST_FILE"
cp "$temp_cast" "$CAST_FILE"
note "SVG image: $temp_svg$SVG_FILE"
cp "$temp_svg" "$SVG_FILE"
note "PNG image: $temp_png$PNG_FILE"
cp "$temp_png" "$PNG_FILE"
# Copy the processed files.
info "Copying processed files"
note "JSON cast: $temp_cast$CAST_FILE"
mkdir -p "$(dirname "$CAST_FILE")"
cp "$temp_cast" "$CAST_FILE"
note "SVG image: $temp_svg$SVG_FILE"
mkdir -p "$(dirname "$SVG_FILE")"
cp "$temp_svg" "$SVG_FILE"
note "PNG image: $temp_png$PNG_FILE"
mkdir -p "$(dirname "$PNG_FILE")"
cp "$temp_png" "$PNG_FILE"
🤖 Prompt for AI Agents
In .vortex/docs/.utils/update-installer-video.sh around lines 424 to 432, the
script copies temp files to their destination paths but does not ensure the
destination directories exist; before each cp call, create the parent directory
for $CAST_FILE, $SVG_FILE and $PNG_FILE (e.g. use mkdir -p "$(dirname
"$CAST_FILE")" etc.), then perform the copy and propagate failures (exit
non‑zero or check cp return) so missing dirs won't cause silent failures.

const link = document.createElement('link');
link.rel = 'stylesheet';
link.href =
'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css';
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Pin CDN version to a specific patch for reproducibility.

Floating @3 can change behavior unexpectedly; pin to an exact version or vend via npm.

Would you like me to update both URLs to the latest stable (e.g., @3.x.y) after confirming the current version?

Also applies to: 37-37


🌐 Web query:

What is the latest stable version of asciinema-player (v3) on jsDelivr/NPM?

💡 Result:

The latest stable asciinema-player v3 is 3.10.0. [1][2][3]

Sources:

  • npm package page (shows v3.10.0). [1]
  • jsDelivr package page (version 3.10.0). [2]
  • asciinema announcement (v3.10 release). [3]

Pin CDN URLs to asciinema-player v3.10.0
The latest stable v3 is 3.10.0; update both CDN imports in .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (lines 29 & 37) from @3 to @3.10.0.

🤖 Prompt for AI Agents
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around lines 29
and 37: the CDN imports currently use the open major tag `@3`; update both
import URLs to pin the exact stable version `@3.10.0` instead of `@3` so the
file imports the specific asciinema-player v3.10.0 assets; ensure both
occurrences (line 29 and line 37) are changed to use `@3.10.0`.

Comment on lines 40 to 51
const options = {
autoPlay,
loop,
preload,
controls,
theme,
};

// Add poster if provided
if (poster) {
options.poster = poster;
}

// Add startAt if provided (for poster frame selection)
if (startAt !== undefined) {
options.startAt = startAt;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Deduplicate option-building.

Build the options object once with conditional spreads to avoid drift.

Apply (covered in previous diff for first branch); do similarly here:

-          if (containerRef.current && window.AsciinemaPlayer) {
-            const options = {
-              autoPlay,
-              loop,
-              preload,
-              controls,
-              theme,
-            };
-
-            // Add poster if provided
-            if (poster) {
-              options.poster = poster;
-            }
-
-            // Add startAt if provided (for poster frame selection)
-            if (startAt !== undefined) {
-              options.startAt = startAt;
-            }
-
-            window.AsciinemaPlayer.create(src, containerRef.current, options);
-          }
+          if (containerRef.current && window.AsciinemaPlayer) {
+            const options = { autoPlay, loop, preload, controls, theme, ...(poster ? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) };
+            containerRef.current.innerHTML = '';
+            window.AsciinemaPlayer.create(src, containerRef.current, options);
+          }

Also applies to: 65-82

🤖 Prompt for AI Agents
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around lines 40
to 56 (and similarly 65 to 82): the options object is being built in stages
which can drift; instead construct it once using a single object literal with
conditional spreads (e.g. { autoPlay, loop, preload, controls, theme, ...(poster
? { poster } : {}), ...(startAt !== undefined ? { startAt } : {}) }) so remove
the multi-step mutation and replace with a single declaration that conditionally
includes poster and startAt.

options.startAt = startAt;
}

window.AsciinemaPlayer.create(src, containerRef.current, options);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent duplicate DOM nodes: clear container before creating the player.

Without clearing, repeated renders stack players.

Apply (included in previous diffs):

-              window.AsciinemaPlayer.create(src, containerRef.current, options);
+              containerRef.current.innerHTML = '';
+              window.AsciinemaPlayer.create(src, containerRef.current, options);

(and same in the already-loaded branch)

Also applies to: 83-83

🤖 Prompt for AI Agents
In .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around line 58
(and similarly at line 83), the Asciinema player is created into the container
without clearing existing DOM nodes, causing duplicate stacked players on
repeated renders; before calling window.AsciinemaPlayer.create(src,
containerRef.current, options) clear the container (remove all child nodes or
set containerRef.current.innerHTML = ''), then proceed to create the player so
only a single player DOM exists.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1951-ascii-cinema branch from 1070c96 to 22c2360 Compare September 2, 2025 08:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (4)
.vortex/docs/content/tools/drush.mdx (1)

55-60: Add pygmy prerequisite note
Pygmy must be installed to use pygmy addkey—it’s Lagoon’s lightweight local development tool (lagoon.sh).

# or
-pygmy addkey --key ~/.ssh/path/to/key  # Run on the host to execute remote Drush commands from inside the container (including ahoy drush).
+pygmy addkey --key ~/.ssh/path/to/key  # Requires pygmy (Lagoon local tooling). Run on the host to execute remote Drush commands from inside the container (including ahoy drush).
.vortex/docs/content/contributing/maintenance/release.mdx (1)

17-19: Fix grammar in Step 11.

Use plural without “a” and add “the” before the template.

-11. Create a new release notes using [release template](./release_template.md).
+11. Create new release notes using [the release template](./release_template.md).
.vortex/docs/content/getting-started/installation.mdx (2)

13-15: Fix grammar (“an in interactive” → “an interactive”).

-We automated the first part by providing an in interactive installer. The second
+We automated the first part by providing an interactive installer. The second

1-4: Silence cspell for domain-specific terms (“Asciinema”).

Fixes current “Vortex - Test docs” pipeline failures.

 ---
 sidebar_position: 3
 ---
+
+<!-- cspell:words Asciinema asciinema -->
♻️ Duplicate comments (19)
.vortex/docs/.utils/update-installer-video.sh (11)

17-19: Dangerous default WORKSPACE_DIR + unconditional rm -rf can wipe user data. Guard creation/cleanup.

Don’t default to $HOME/www/demo; only delete directories the script created.

-# Directory for temporary workspace.
-WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"
+# Directory for temporary workspace (set in main; may be overridden via env).
+# WORKSPACE_DIR is intentionally not defaulted here to avoid deleting user dirs.
-  WORKSPACE_DIR="${WORKSPACE_DIR:-"$(mktemp -d -t vortex-installer-recording-XXXXXX)"}"
-  [ ! -d "$WORKSPACE_DIR" ] && mkdir -p "$WORKSPACE_DIR"
+  local CREATED_TMPDIR=""
+  if [[ -z "${WORKSPACE_DIR:-}" ]]; then
+    CREATED_TMPDIR="$(mktemp -d -t vortex-installer-recording-XXXXXX)"
+    WORKSPACE_DIR="$CREATED_TMPDIR"
+  else
+    mkdir -p "$WORKSPACE_DIR"
+  fi
@@
-  cleanup() {
-    info "Cleaning up workspace directory"
-    note "Removing: ${WORKSPACE_DIR}"
-    rm -rf "$WORKSPACE_DIR"
-  }
+  cleanup() {
+    # Only remove if we created it in this run
+    if [[ -n "$CREATED_TMPDIR" && "$WORKSPACE_DIR" == "$CREATED_TMPDIR" ]]; then
+      info "Cleaning up workspace directory"
+      note "Removing: ${WORKSPACE_DIR}"
+      rm -rf "$WORKSPACE_DIR"
+    fi
+  }

Also applies to: 372-387


50-69: Add node to dependency checks and improve install hints.

npx requires node; add it and show CI-friendly install commands.

-  local required_commands=("asciinema" "expect" "php" "composer" "npx")
+  local required_commands=("asciinema" "expect" "php" "composer" "node" "npx")
@@
-    echo "  brew install asciinema expect composer  # macOS" >&2
-    echo "  apt-get install asciinema expect-dev composer  # Ubuntu/Debian" >&2
+    echo "  brew install asciinema expect composer node  # macOS" >&2
+    echo "  apt-get update -y && apt-get install -y --no-install-recommends asciinema expect-dev composer nodejs npm  # Ubuntu/Debian" >&2

76-83: Ensure destination dir exists before copy.

Prevents failures when parent dir is missing.

-    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+    mkdir -p "$(dirname "$INSTALLER_DOCS")"
+    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"

95-110: Harden composer for CI (non-interactive, quieter).

Avoids interactivity and noisy logs.

-    info "Running composer install..."
-    if ! composer install; then
+    info "Running composer install..."
+    if ! composer install --no-interaction --no-ansi --no-progress --prefer-dist; then
@@
-    info "Running composer build..."
-    if ! composer build; then
+    info "Running composer build..."
+    if ! composer build -n; then

113-120: Ensure destination dir exists before copy (post-build).

-      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+      mkdir -p "$(dirname "$INSTALLER_DOCS")"
+      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"

185-190: Use Expect/Tcl ‘after’ instead of ‘sleep’ for portability/precision.

-    sleep $prompt_delay
+    after [expr {int($prompt_delay * 1000)}]

286-291: Guard copy: fail early if installer source missing.

-  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"
+  if [ ! -f "$INSTALLER_DOCS" ]; then
+    fail "Installer not found at: $INSTALLER_DOCS"
+    return 1
+  fi
+  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"

311-319: Make sed replacement broader and -i portable (macOS/Linux).

-  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
-  sed "${sed_opts[@]}" -E 's|/Users/[^/]+/|/home/user/|g' "$output_cast"
+  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
+  sed "${sed_opts[@]}" -E 's|/(Users|home)/[^/]+/|/home/user/|g' "$output_cast"

320-341: Make all npx calls non-interactive (-y) for CI stability.

-  if ! npx svg-term --out="$output_svg" <"$output_cast"; then
+  if ! npx -y svg-term --out="$output_svg" <"$output_cast"; then
@@
-  if ! npx svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
+  if ! npx -y svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
@@
-  if ! npx sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then
+  if ! npx -y sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then

418-424: Create destination dirs before copying outputs.

-  cp "$temp_cast" "$CAST_FILE"
+  mkdir -p "$(dirname "$CAST_FILE")"
+  cp "$temp_cast" "$CAST_FILE"
@@
-  cp "$temp_svg" "$SVG_FILE"
+  mkdir -p "$(dirname "$SVG_FILE")"
+  cp "$temp_svg" "$SVG_FILE"
@@
-  cp "$temp_png" "$PNG_FILE"
+  mkdir -p "$(dirname "$PNG_FILE")"
+  cp "$temp_png" "$PNG_FILE"

12-13: Enable pipefail for safer pipelines.

Prevents silent failures when any command in a pipeline fails.

-set -eu
+set -euo pipefail
.github/workflows/vortex-release-docs.yml (1)

135-140: Make apt non-interactive/reliable and guard long-running script with timeout.

Prevents hangs and flaky installs on runners.

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.github/workflows/vortex-test-installer.yml (2)

33-37: Prefer a moving LTS range for Node to reduce churn.

-          node-version: 22.17.1
+          node-version: '22.x'

59-64: Same apt reliability/timeout fixes as release workflow.

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.vortex/docs/content/getting-started/installation.mdx (2)

5-5: Avoid SSR issues: render player client-side with BrowserOnly.

Prevents hydration warnings if the player touches window/DOM.

-import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
+import BrowserOnly from '@docusaurus/BrowserOnly';
+import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';

27-35: Wrap AsciinemaPlayer with BrowserOnly.

-    <AsciinemaPlayer
-      src="/img/installer.json"
-      poster="npt:0:1"
-      autoPlay={false}
-      loop={false}
-      preload={true}
-      controls={true}
-      theme="asciinema"
-    />
+    <BrowserOnly>
+      {() => (
+        <AsciinemaPlayer
+          src="/img/installer.json"
+          poster="npt:0:1"
+          autoPlay={false}
+          loop={false}
+          preload={true}
+          controls={true}
+          theme="asciinema"
+        />
+      )}
+    </BrowserOnly>
.vortex/.ahoy.yml (1)

110-115: Harden the command wrapper and avoid fragile relative paths.

Fail fast and resolve the script from repo root so it works from any CWD.

   update-installer-video:
     usage: Update installer video in the documentation.
     cmd: |
-      cd docs/.utils
-      ./update-installer-video.sh
+      set -euo pipefail
+      cd "$(git rev-parse --show-toplevel)/docs/.utils"
+      ./update-installer-video.sh
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1)

91-93: Add effect cleanup to prevent duplicate players and memory leaks.

-    loadAsciinemaPlayer();
-  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);
+    loadAsciinemaPlayer();
+    return () => {
+      if (containerRef.current) containerRef.current.innerHTML = '';
+    };
+  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);
.vortex/docs/static/img/installer.json (1)

1-6: Fix Biome parse failures: rename .json (JSON Lines) to .cast or ignore the file.

This asciinema v2 recording is newline-delimited JSON, not a single JSON document; Biome flags it.

Option A (preferred): rename and update references

- src="/img/installer.json"
+ src="/img/installer.cast"

Option B: ignore in Biome (new file at repo root)

+.biomeignore
+/.vortex/docs/static/img/installer.json
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1070c96 and 22c2360.

⛔ Files ignored due to path filters (2)
  • .vortex/docs/static/img/installer.png is excluded by !**/*.png
  • .vortex/docs/static/img/installer.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • .github/workflows/vortex-release-docs.yml (1 hunks)
  • .github/workflows/vortex-test-installer.yml (2 hunks)
  • .vortex/.ahoy.yml (1 hunks)
  • .vortex/docs/.gitignore (1 hunks)
  • .vortex/docs/.utils/update-installer-video.sh (1 hunks)
  • .vortex/docs/content/contributing/maintenance/release.mdx (1 hunks)
  • .vortex/docs/content/getting-started/installation.mdx (2 hunks)
  • .vortex/docs/content/tools/drush.mdx (1 hunks)
  • .vortex/docs/content/workflows/deployment.mdx (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/index.js (1 hunks)
  • .vortex/docs/static/img/installer.json (1 hunks)
  • README.md (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.

Applied to files:

  • .vortex/docs/.gitignore
🪛 LanguageTool
.vortex/docs/content/contributing/maintenance/release.mdx

[grammar] ~18-~18: Use articles correctly
Context: ...hoy update-installer-video`. 11. Create a new release notes using [release templa...

(QB_NEW_EN_OTHER_ERROR_IDS_11)


[grammar] ~18-~18: There might be a mistake here.
Context: ...elease template](./release_template.md).

(QB_NEW_EN)

.vortex/docs/content/getting-started/installation.mdx

[grammar] ~5-~5: Use correct spacing
Context: ... '@site/src/components/AsciinemaPlayer'; # Installation The installation process c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 Biome (2.1.2)
.vortex/docs/static/img/installer.json

[error] 1-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-18: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 18-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-29: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 29-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-40: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 40-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-50: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 50-51: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 51-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-65: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 65-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-75: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 75-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-84: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 84-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-89: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 89-91: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 91-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-97: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 97-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-112: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 112-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-116: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 116-120: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 120-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-124: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 124-125: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 125-135: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 135-136: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 136-137: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 137-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 151-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🪛 markdownlint-cli2 (0.17.2)
README.md

54-54: Inline HTML
Element: img

(MD033, no-inline-html)

🪛 GitHub Actions: Vortex - Test docs
.vortex/docs/content/getting-started/installation.mdx

[error] 5-5: CSpell: Unknown word 'Asciinema' detected on line 5, column 8.


[error] 5-5: CSpell: Unknown word 'Asciinema' detected on line 5, column 51.


[error] 27-27: CSpell: Unknown word 'Asciinema' detected on line 27, column 6.


[error] 34-34: CSpell: Unknown word 'asciinema' detected on line 34, column 14.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: build (1)
  • GitHub Check: build (0)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.4)
🔇 Additional comments (3)
.vortex/docs/content/workflows/deployment.mdx (1)

30-33: Blank line before list is correct.

The added blank line before the branches list fixes Markdown rendering edge cases. LGTM.

.vortex/docs/.gitignore (1)

24-26: Rule is clear and scoped.

Ignoring the copied installer file under docs is reasonable and the explanatory comment helps. LGTM.

.vortex/docs/src/components/AsciinemaPlayer/index.js (1)

1-1: LGTM: clean barrel export.

Comment on lines +25 to +29
if (!document.querySelector('link[href*="asciinema-player.css"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href =
'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css';
document.head.appendChild(link);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Pin CSS CDN to an exact version for reproducibility.

-          link.href =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css';
+          link.href =
+            'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.css';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!document.querySelector('link[href*="asciinema-player.css"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href =
'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css';
document.head.appendChild(link);
}
if (!document.querySelector('link[href*="asciinema-player.css"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href =
'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.css';
document.head.appendChild(link);
}
🤖 Prompt for AI Agents
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around lines 25
to 31: the CSS CDN URL currently uses a floating range (@3) which can change
over time; update the link.href to reference an exact published version (replace
"@3" with a specific semver like "@3.1.0" or the project-approved release tag)
so the CSS is reproducible, and commit that pinned URL (and update any comment
noting the pinned version to remind to update intentionally).

Comment on lines +54 to +55
<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Silence MD033 or switch to Markdown image.

Inline HTML triggers markdownlint MD033. Either disable the rule for this line or use Markdown image syntax.

Option A (disable rule, keep HTML for styling control):

+<!-- markdownlint-disable-next-line MD033 -->
 <img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />

Option B (Markdown image):

-<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
+![Vortex installer screenshot](.vortex/docs/static/img/installer.svg)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
![Vortex installer screenshot](.vortex/docs/static/img/installer.svg)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

54-54: Inline HTML
Element: img

(MD033, no-inline-html)

🤖 Prompt for AI Agents
In README.md around lines 54-55, the inline HTML image tag triggers markdownlint
rule MD033; either disable MD033 for that line with an HTML comment marker or
replace the HTML with standard Markdown image syntax (![alt](path)) to avoid the
lint error — choose the approach you prefer, add the corresponding change on
that line, and if disabling the rule include a brief comment (e.g. <!--
markdownlint-disable MD033 --> before or on the same line) so the linter skips
it.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1951-ascii-cinema branch from 22c2360 to f832e49 Compare September 2, 2025 09:55
@github-actions github-actions bot temporarily deployed to commit September 2, 2025 09:57 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

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

⚠️ Outside diff range comments (9)
.vortex/docs/tests/jest-setup.js (1)

4-28: Do not override native EventTarget if present.

JSDOM increasingly provides EventTarget; unconditional override can break libraries relying on the native interface. Guard the polyfill.

Apply:

-// Polyfill for EventTarget in jsdom environment
-global.EventTarget = class EventTarget {
+// Polyfill for EventTarget in jsdom environment
+if (typeof global.EventTarget === 'undefined') {
+  global.EventTarget = class EventTarget {
     constructor() {
       this.listeners = {};
     }
 
     addEventListener(type, listener) {
       if (!this.listeners[type]) {
         this.listeners[type] = [];
       }
       this.listeners[type].push(listener);
     }
 
     removeEventListener(type, listener) {
       if (this.listeners[type]) {
         this.listeners[type] = this.listeners[type].filter(l => l !== listener);
       }
     }
 
     dispatchEvent(event) {
       if (this.listeners[event.type]) {
         this.listeners[event.type].forEach(listener => listener(event));
       }
       return true;
     }
-};
+  };
+}
.vortex/docs/content/tools/drush.mdx (1)

55-60: Clarify pygmy usage context in the SSH key example.

Readers may not have pygmy installed; add a note inline to indicate it’s optional and requires pygmy.

 ```shell
 ssh-add ~/.ssh/path/to/key             # Run on host to run remote Drush commands from host.
 # or
-pygmy addkey --key ~/.ssh/path/to/key  # Run on the host to execute remote Drush commands from inside the container (including ahoy drush).
+pygmy addkey --key ~/.ssh/path/to/key  # Requires pygmy; use if your stack includes pygmy for forwarding keys into containers.

</blockquote></details>
<details>
<summary>.vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (1)</summary><blockquote>

`475-493`: **Rework or remove the null-ref test (tests/unit/ContentExtractor/ContentExtractor.test.js:475-493)**  
This test renders a standalone component and never exercises the `if (!contentRef.current)` guard in ContentExtractor.js (lines 41–42), so it’s redundant. Rewrite it to render `ContentExtractor` with a null ref to cover that branch or remove it.

</blockquote></details>
<details>
<summary>.vortex/docs/tests/unit/Card/CardGrid.test.js (2)</summary><blockquote>

`201-204`: **Avoid brittle assertion on trailing space in className.**

Comparing raw className with a trailing space is fragile and implementation-dependent. Assert classes instead.


Apply:

```diff
-      expect(gridElement.className).toBe('cards-grid ');
+      expect(gridElement).toHaveClass('cards-grid');
+      expect(gridElement.classList).toHaveLength(1);

206-216: Test name mentions boolean but only asserts numeric className.

Either add a boolean case or rename the test to reflect what’s exercised.

Apply either:

-    test('handles numeric and boolean className values', () => {
+    test('handles numeric className values', () => {

Or add a boolean check:

-      const { container } = render(
+      const { container, rerender } = render(
         <CardGrid className={123}>
           <div>Test child</div>
         </CardGrid>
       );
+      rerender(
+        <CardGrid className={true}>
+          <div>Test child</div>
+        </CardGrid>
+      );
+      expect(container.querySelector('.cards-grid')).toHaveClass('true');
.github/workflows/vortex-release-docs.yml (2)

115-119: Fix artifact path: default download-artifact extracts into a folder named after the artifact.

cp is pointing to ../installer.phar, but the file will be at ../vortex-installer/installer.phar by default.

Apply:

       - name: Copy installer to docs
         run: |
-          cp ../installer.phar ../.vortex/docs/static/install
+          cp ../vortex-installer/installer.phar ../.vortex/docs/static/install
           php ../.vortex/docs/static/install --version
         working-directory: .vortex/docs

102-106: Use a moving Node range to reduce maintenance.

Prefer 22.x or lts/* to auto-receive patch updates.

       - name: Setup Node.js
         uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
-          node-version: 22.17.1
+          node-version: '22.x'
.vortex/docs/content/getting-started/installation.mdx (2)

13-15: Fix typo and spacing in user-facing docs.

“an in interactive” → “an interactive”.

-We automated the first part by providing an in interactive installer. The second
+We automated the first part by providing an interactive installer. The second

98-99: Minor spacing nit.

Remove double space before “and”.

-Depending on the services you are using, you will need to set up the
-integrations with your hosting provider, continuous integration provider,  and
+Depending on the services you are using, you will need to set up the
+integrations with your hosting provider, continuous integration provider, and
♻️ Duplicate comments (18)
.vortex/docs/.gitignore (1)

24-25: Disambiguate whether “static/install” is a file or directory.

If it’s a directory, add a trailing slash; if it’s a single file, keep as-is. This mirrors a prior review point.

- static/install
+ static/install/
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (3)

23-29: Pin CDN assets and dedupe via stable selectors.

Floating @3 can drift; pin to an exact version and give the CSS link a stable id so it isn’t injected multiple times.

Apply:

-        if (!document.querySelector('link[href*="asciinema-player.css"]')) {
+        if (!document.getElementById('asciinema-player-css')) {
           const link = document.createElement('link');
           link.rel = 'stylesheet';
-          link.href =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css';
+          link.id = 'asciinema-player-css';
+          link.href =
+            'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.css';
           document.head.appendChild(link);
         }
-          script.src =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
+          script.src =
+            'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.js';
+          script.async = true;
What is the latest stable version of asciinema-player v3 on npm/jsDelivr?

Also applies to: 33-35


31-77: Harden dynamic loader: dedupe, await load/error, clear container, and add cleanup.

Prevents duplicate scripts, races, and stacked players; surfaces load failures; tears down on unmount.

Apply:

-        if (!window.AsciinemaPlayer) {
-          const script = document.createElement('script');
-          script.src =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.js';
-          script.onload = () => {
-            if (containerRef.current && window.AsciinemaPlayer) {
-              const options = {
-                autoPlay,
-                loop,
-                preload,
-                controls,
-                theme,
-              };
-
-              if (poster) {
-                options.poster = poster;
-              }
-
-              if (startAt !== undefined) {
-                options.startAt = startAt;
-              }
-
-              window.AsciinemaPlayer.create(src, containerRef.current, options);
-            }
-          };
-          document.head.appendChild(script);
-        } else {
-          if (containerRef.current && window.AsciinemaPlayer) {
-            const options = {
-              autoPlay,
-              loop,
-              preload,
-              controls,
-              theme,
-            };
-
-            if (poster) {
-              options.poster = poster;
-            }
-
-            if (startAt !== undefined) {
-              options.startAt = startAt;
-            }
-
-            window.AsciinemaPlayer.create(src, containerRef.current, options);
-          }
-        }
+        if (!window.AsciinemaPlayer) {
+          if (!window.__asciinemaLoader) {
+            const existing = document.querySelector('script[src*="asciinema-player.min.js"]');
+            window.__asciinemaLoader = new Promise((resolve, reject) => {
+              const onLoad = () => resolve();
+              const onError = () => reject(new Error('Failed to load Asciinema player script'));
+              if (existing) {
+                existing.addEventListener('load', onLoad, { once: true });
+                existing.addEventListener('error', onError, { once: true });
+              } else {
+                const script = document.createElement('script');
+                script.src = 'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.js';
+                script.async = true;
+                script.onload = onLoad;
+                script.onerror = onError;
+                document.head.appendChild(script);
+              }
+            });
+          }
+          try {
+            await window.__asciinemaLoader;
+          } catch (e) {
+            console.error(e);
+            return;
+          }
+        }
+
+        if (containerRef.current && window.AsciinemaPlayer) {
+          const options = {
+            autoPlay, loop, preload, controls, theme,
+            ...(poster ? { poster } : {}),
+            ...(startAt !== undefined ? { startAt } : {}),
+          };
+          containerRef.current.innerHTML = '';
+          window.AsciinemaPlayer.create(src, containerRef.current, options);
+        }
@@
-    loadAsciinemaPlayer();
-  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);
+    loadAsciinemaPlayer();
+    return () => {
+      if (containerRef.current) containerRef.current.innerHTML = '';
+    };
+  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);

Also applies to: 83-86


37-51: Deduplicate option building.

Construct options once using conditional spreads in both branches.

Apply (already included in previous diff): create a single options object with conditional poster/startAt and reuse it.

Also applies to: 59-75

.vortex/docs/static/img/installer.json (1)

1-152: Biome parse errors: asciinema v2 is JSON Lines, not JSON. Rename to .cast or ignore.

This NDJSON format breaks JSON linters. Prefer renaming to installer.cast and update references; otherwise add to .biomeignore.

Apply one of:

  • Rename + update references (preferred):
- src="/img/installer.json"
+ src="/img/installer.cast"
  • Ignore file (repo root .biomeignore):
+/.vortex/docs/static/img/installer.json
#!/bin/bash
# Find references to the current path to update them after rename.
rg -n "/img/installer\.json" -C2 .vortex/docs
.vortex/docs/.utils/update-installer-video.sh (7)

50-69: Include node in dependency checks and update install hints.

npx depends on node; add it and make hints actionable.

Apply:

-  local required_commands=("asciinema" "expect" "php" "composer" "npx")
+  local required_commands=("asciinema" "expect" "php" "composer" "node" "npx")
@@
-    echo "  brew install asciinema expect composer  # macOS" >&2
-    echo "  apt-get install asciinema expect-dev composer  # Ubuntu/Debian" >&2
+    echo "  brew install asciinema expect composer node  # macOS" >&2
+    echo "  apt-get update && apt-get install -y asciinema expect-dev composer nodejs npm  # Ubuntu/Debian" >&2

76-83: Ensure destination directories exist before copying.

Prevents cp failures when target dirs are missing.

Apply:

-    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+    mkdir -p "$(dirname "$INSTALLER_DOCS")"
+    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
@@
-      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+      mkdir -p "$(dirname "$INSTALLER_DOCS")"
+      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
@@
-  cp "$temp_cast" "$CAST_FILE"
+  mkdir -p "$(dirname "$CAST_FILE")"
+  cp "$temp_cast" "$CAST_FILE"
@@
-  cp "$temp_svg" "$SVG_FILE"
+  mkdir -p "$(dirname "$SVG_FILE")"
+  cp "$temp_svg" "$SVG_FILE"
@@
-  cp "$temp_png" "$PNG_FILE"
+  mkdir -p "$(dirname "$PNG_FILE")"
+  cp "$temp_png" "$PNG_FILE"

Also applies to: 113-120, 418-425


95-105: Harden composer for CI (non-interactive, quieter).

Avoid prompts and reduce noise.

Apply:

-    if ! composer install; then
+    if ! composer install --no-interaction --no-ansi --no-progress; then
@@
-    if ! composer build; then
+    if ! composer build -n; then

286-290: Guard copy into workspace.

Fail clearly if installer is missing.

Apply:

-  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"
+  if [ ! -f "$INSTALLER_DOCS" ]; then
+    fail "Installer not found at: $INSTALLER_DOCS"
+    return 1
+  fi
+  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"

317-319: Make sed replacement more robust (mac/Linux) and less specific to home path.

Covers /Users and /home, preserves -i semantics.

Apply:

-  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
-  sed "${sed_opts[@]}" -E 's|/Users/[^/]+/|/home/user/|g' "$output_cast"
+  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
+  sed "${sed_opts[@]}" -E 's|/(Users|home)/[^/]+/|/home/user/|g' "$output_cast"

323-341: Make all npx calls non-interactive to avoid CI stalls.

Add -y to auto-accept installs.

Apply:

-  if ! npx svg-term --out="$output_svg" <"$output_cast"; then
+  if ! npx -y svg-term --out="$output_svg" <"$output_cast"; then
@@
-  if ! npx svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
+  if ! npx -y svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
@@
-  if ! npx sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then
+  if ! npx -y sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then

185-190: Use Expect ‘after’ instead of ‘sleep’ inside the generated script.

sleep isn’t portable here; ‘after’ works reliably.

Apply (inside heredoc):

-    sleep $prompt_delay
+    after [expr {int($prompt_delay * 1000)}]
README.md (1)

54-55: markdownlint MD033: replace inline HTML img or disable rule.

Switch to Markdown image syntax or silence MD033 for this line.

Apply one of:

-<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
+![Vortex installer screenshot](.vortex/docs/static/img/installer.svg)

or

+<!-- markdownlint-disable-next-line MD033 -->
 <img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
.github/workflows/vortex-release-docs.yml (1)

135-139: Make apt non-interactive and guard the long-running script with a timeout.

Prevents hangs/404s on runners; ensures the step doesn’t block the job indefinitely.

Apply:

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.github/workflows/vortex-test-installer.yml (2)

33-37: Prefer a moving Node range.

       - name: Setup Node.js
         uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
-          node-version: 22.17.1
+          node-version: '22.x'

59-63: Make apt non-interactive and add timeout for reliability.

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.vortex/docs/content/getting-started/installation.mdx (1)

5-5: Avoid SSR hydration issues: render the player client-only.

Wrap with Docusaurus BrowserOnly (or ensure the component is SSR-safe).

+import BrowserOnly from '@docusaurus/BrowserOnly';
 import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
@@
-    <AsciinemaPlayer
-      src="/img/installer.json"
-      poster="npt:0:1"
-      autoPlay={false}
-      loop={false}
-      preload={true}
-      controls={true}
-      theme="asciinema"
-    />
+    <BrowserOnly>
+      {() => (
+        <AsciinemaPlayer
+          src="/img/installer.json"
+          poster="npt:0:1"
+          autoPlay={false}
+          loop={false}
+          preload={true}
+          controls={true}
+          theme="asciinema"
+        />
+      )}
+    </BrowserOnly>

Also applies to: 27-35

.vortex/.ahoy.yml (1)

110-115: Harden wrapper: use strict modes and absolute path; forward args.

Current command assumes CWD and lacks fail-fast. Make it robust and pass through args.

   update-installer-video:
     usage: Update installer video in the documentation.
     cmd: |
-      cd docs/.utils
-      ./update-installer-video.sh
+      set -euo pipefail
+      repo_root="$(git rev-parse --show-toplevel)"
+      cd "${repo_root}/docs/.utils"
+      exec ./update-installer-video.sh "$@"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 22c2360 and f832e49.

⛔ Files ignored due to path filters (2)
  • .vortex/docs/static/img/installer.png is excluded by !**/*.png
  • .vortex/docs/static/img/installer.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • .github/workflows/vortex-release-docs.yml (1 hunks)
  • .github/workflows/vortex-test-installer.yml (2 hunks)
  • .vortex/.ahoy.yml (1 hunks)
  • .vortex/docs/.gitignore (1 hunks)
  • .vortex/docs/.prettierignore (1 hunks)
  • .vortex/docs/.utils/update-installer-video.sh (1 hunks)
  • .vortex/docs/content/contributing/maintenance/release.mdx (1 hunks)
  • .vortex/docs/content/getting-started/installation.mdx (2 hunks)
  • .vortex/docs/content/tools/drush.mdx (1 hunks)
  • .vortex/docs/content/workflows/deployment.mdx (1 hunks)
  • .vortex/docs/cspell.json (1 hunks)
  • .vortex/docs/package.json (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/index.js (1 hunks)
  • .vortex/docs/static/img/installer.json (1 hunks)
  • .vortex/docs/tests/fixtures/test-cast.json (1 hunks)
  • .vortex/docs/tests/jest-setup.js (1 hunks)
  • .vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js (1 hunks)
  • .vortex/docs/tests/unit/Card/Card.integration.test.js (10 hunks)
  • .vortex/docs/tests/unit/Card/Card.test.js (16 hunks)
  • .vortex/docs/tests/unit/Card/CardGrid.test.js (10 hunks)
  • .vortex/docs/tests/unit/Card/index.test.js (1 hunks)
  • .vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (23 hunks)
  • .vortex/docs/tests/unit/VerticalTabs/MarkerComponents.test.js (2 hunks)
  • .vortex/docs/tests/unit/VerticalTabs/VerticalTabs.test.js (6 hunks)
  • .vortex/docs/tests/unit/VerticalTabs/index.test.js (2 hunks)
  • README.md (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.

Applied to files:

  • .vortex/docs/.prettierignore
  • .vortex/docs/.gitignore
🧬 Code graph analysis (5)
.vortex/docs/tests/unit/VerticalTabs/MarkerComponents.test.js (2)
.vortex/docs/src/components/VerticalTabs/VerticalTab.js (1)
  • VerticalTab (7-10)
.vortex/docs/src/components/VerticalTabs/VerticalTabPanel.js (1)
  • VerticalTabPanel (7-10)
.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js (1)
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1)
  • AsciinemaPlayer (3-87)
.vortex/docs/tests/unit/VerticalTabs/index.test.js (3)
.vortex/docs/src/components/VerticalTabs/VerticalTabs.js (1)
  • VerticalTabs (83-210)
.vortex/docs/src/components/VerticalTabs/VerticalTab.js (1)
  • VerticalTab (7-10)
.vortex/docs/src/components/VerticalTabs/VerticalTabPanel.js (1)
  • VerticalTabPanel (7-10)
.vortex/docs/.utils/update-installer-video.sh (2)
.vortex/installer/src/Utils/Config.php (1)
  • set (82-93)
.vortex/installer/src/Utils/Validator.php (1)
  • dirname (32-34)
.vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (1)
.vortex/docs/src/components/ContentExtractor.js (1)
  • ContentExtractor (29-135)
🪛 LanguageTool
.vortex/docs/content/contributing/maintenance/release.mdx

[grammar] ~18-~18: Use articles correctly
Context: ...hoy update-installer-video`. 11. Create a new release notes using [release templa...

(QB_NEW_EN_OTHER_ERROR_IDS_11)


[grammar] ~18-~18: There might be a mistake here.
Context: ...elease template](./release_template.md).

(QB_NEW_EN)

.vortex/docs/content/getting-started/installation.mdx

[grammar] ~5-~5: Use correct spacing
Context: ... '@site/src/components/AsciinemaPlayer'; # Installation The installation process c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 Biome (2.1.2)
.vortex/docs/tests/fixtures/test-cast.json

[error] 1-2: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 2-3: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 3-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 7-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-9: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 9-10: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 10-11: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 11-12: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 12-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-15: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 15-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-17: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

.vortex/docs/static/img/installer.json

[error] 1-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-18: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 18-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-29: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 29-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-40: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 40-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-50: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 50-51: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 51-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-65: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 65-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-75: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 75-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-84: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 84-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-89: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 89-91: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 91-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-97: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 97-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-112: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 112-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-116: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 116-120: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 120-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-124: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 124-125: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 125-135: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 135-136: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 136-137: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 137-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 151-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🪛 markdownlint-cli2 (0.17.2)
README.md

54-54: Inline HTML
Element: img

(MD033, no-inline-html)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (0)
🔇 Additional comments (17)
.vortex/docs/content/workflows/deployment.mdx (1)

32-32: LGTM: readability tweak.

The added blank line improves list separation; no content changes.

.vortex/docs/tests/unit/VerticalTabs/VerticalTabs.test.js (5)

2-8: LGTM: import formatting.

Readability improved; all imported symbols are used.


448-450: LGTM: multi-line chaining for clarity.

No behavioral change; selection remains precise.


505-508: LGTM: explicit expectation arguments.

Clearer assertion for event listener wiring.


546-549: LGTM: consistent selection pattern.

Matches the earlier style for active tab checks.


646-649: LGTM: listener add/remove assertions.

Good coverage of mount/unmount behavior.

Also applies to: 655-658

.vortex/docs/tests/unit/VerticalTabs/MarkerComponents.test.js (1)

31-36: LGTM — formatting-only edits.

Whitespace adjustments don’t affect behavior; tests still validate marker components correctly.

Also applies to: 65-70, 72-72

.vortex/docs/tests/unit/Card/index.test.js (1)

33-33: LGTM — trailing newline only.

No semantic changes.

.vortex/docs/tests/unit/Card/Card.test.js (1)

20-23: LGTM — formatting-only refactors across tests.

Line wraps and JSX inlining don’t change behavior; assertions remain equivalent.

Also applies to: 41-41, 44-44, 51-51, 55-55, 71-71, 93-93, 95-95, 97-97, 99-101, 129-132, 142-145, 148-148, 179-186, 191-198, 205-205, 217-217, 279-279, 309-309, 339-339, 348-351, 364-368, 376-380, 394-394, 426-426

.vortex/docs/tests/unit/Card/Card.integration.test.js (1)

46-48: LGTM – formatting-only edits.

No behavioral changes; readability improved.

Also applies to: 67-67, 171-173, 229-229, 251-254, 405-406

.vortex/docs/tests/unit/VerticalTabs/index.test.js (1)

1-1: LGTM – whitespace/formatting cleanup.

No logic changes; tests remain clear and focused on export shape.

Also applies to: 20-20

.vortex/docs/package.json (1)

21-22: Prettier ignore covers Asciinema fixture
The existing .vortex/docs/.prettierignore already includes tests/fixtures/*.json, so test-cast.json is excluded from Prettier and will not cause lint failures.

Likely an incorrect or invalid review comment.

.vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (1)

10-12: LGTM – assertions updated to reflect multi-line intro text and new filtering cases.

The tests align with implementation (intro substring match, selector-based slicing, defaults). Good coverage improvements.

Also applies to: 77-87, 118-126, 146-149, 166-171, 186-191, 209-215, 232-237, 246-250, 261-267, 280-285, 302-313, 324-331, 355-361, 374-380, 386-394, 434-446, 463-471, 478-481, 503-507, 512-520, 531-543, 553-561

.vortex/docs/tests/unit/Card/CardGrid.test.js (1)

61-69: Formatting-only refactor looks good.

Inline Card props, fragment/array tweaks, and conditional wrapping don’t change behavior. Tests remain readable and deterministic.

Also applies to: 112-113, 221-229, 255-259, 272-290, 308-314, 451-452

.vortex/docs/src/components/AsciinemaPlayer/index.js (1)

1-1: Barrel export looks good.

Simple, conventional re-export. No issues.

.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js (2)

536-567: Good coverage of the “library already exists” path and options wiring.

Asserts poster/startAt/theme/booleans are forwarded correctly when AsciinemaPlayer is preloaded. Looks solid.


1-3: Ensure '@testing-library/jest-dom' is loaded globally
I didn’t find any import of jest-dom matchers or a Jest setup configuration in the repo; please verify that jest-dom is configured (for example, by adding import '@testing-library/jest-dom' to your Jest setup file and listing it under setupFilesAfterEnv).

package-lock.json
*.md
*.mdx
*.mdx
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Confirm intent to ignore MDX formatting.

Ignoring *.mdx prevents Prettier from standardizing docs formatting. If this wasn’t intentional, drop this line; otherwise, keep as-is.

🤖 Prompt for AI Agents
.vortex/docs/.prettierignore line 8: the repository currently ignores all .mdx
files from Prettier; confirm intent by either removing the "*.mdx" line if you
want Prettier to format MDX files, or keep it but add a short inline comment
above that line explaining why MDX are excluded (e.g., third-party generated,
formatting breaks content) or narrow the pattern to only specific paths that
should remain unformatted.

Comment on lines +12 to +19
set -eu
[ "${VORTEX_DEBUG-}" = "1" ] && set -x

#-------------------------------------------------------------------------------

# Directory for temporary workspace.
WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: default WORKSPACE_DIR + rm -rf can delete user data. Use mktemp and guarded cleanup.

Avoid defaulting to $HOME/www/demo and only delete dirs created by this run.

Apply:

-set -eu
+set -euo pipefail
@@
-# Directory for temporary workspace.
-WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"
+# Directory for temporary workspace (set in main; may be overridden via env).
+# WORKSPACE_DIR is not defaulted here to avoid deleting user dirs.
@@
-  info "Creating temporary workspace"
-  WORKSPACE_DIR="${WORKSPACE_DIR:-"$(mktemp -d -t vortex-installer-recording-XXXXXX)"}"
-  [ ! -d "$WORKSPACE_DIR" ] && mkdir -p "$WORKSPACE_DIR"
+  info "Creating temporary workspace"
+  local CREATED_TMPDIR=""
+  if [[ -z "${WORKSPACE_DIR:-}" ]]; then
+    CREATED_TMPDIR="$(mktemp -d -t vortex-installer-recording-XXXXXX)"
+    WORKSPACE_DIR="$CREATED_TMPDIR"
+  else
+    mkdir -p "$WORKSPACE_DIR"
+  fi
@@
-  cleanup() {
-    info "Cleaning up workspace directory"
-    note "Removing: ${WORKSPACE_DIR}"
-    rm -rf "$WORKSPACE_DIR"
-  }
+  cleanup() {
+    if [[ -n "$CREATED_TMPDIR" && "$WORKSPACE_DIR" == "$CREATED_TMPDIR" ]]; then
+      info "Cleaning up workspace directory"
+      note "Removing: ${WORKSPACE_DIR}"
+      rm -rf "$WORKSPACE_DIR"
+    fi
+  }

Also applies to: 372-387

🤖 Prompt for AI Agents
.vortex/docs/.utils/update-installer-video.sh lines 12-19 (and similarly at
372-387): the script currently defaults WORKSPACE_DIR to $HOME/www/demo and
later uses rm -rf which risks deleting user data; change to create a unique
temporary workspace with mktemp (e.g., mktemp -d) when WORKSPACE_DIR is not
explicitly provided, store a flag indicating the script created it, and on exit
only remove the directory if that flag is set (guarded cleanup); also validate
that WORKSPACE_DIR is not empty and not a root/home path before any destructive
operation to prevent accidental deletion.

"softwareTerms"
],
"words": [
"Asciinema",
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add common variants to reduce false positives.

Consider adding lowercase and related terms used in tooling/docs:

   "words": [
     "Asciinema",
+    "asciinema",
+    "asciicast",
+    "svgterm"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Asciinema",
"Asciinema",
"asciinema",
"asciicast",
"svgterm"
🤖 Prompt for AI Agents
.vortex/docs/cspell.json around line 10: the word "Asciinema" is listed but
lacks common variants causing false positives; add lowercase "asciinema" and any
related terms or common capitalizations used in docs/tooling (e.g., "asciicast")
to the cspell words array so spellchecker recognizes these variants
consistently.

Comment on lines +451 to +456
test('loads CSS and JS assets', async () => {
render(<AsciinemaPlayer src="/fixtures/test-cast.json" />);

// Wait for useEffect
await new Promise(resolve => setTimeout(resolve, 100));

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace arbitrary timeouts with waitFor/fake timers to remove flakiness.

setTimeout-based waits are brittle and slow. Prefer Testing Library’s waitFor (or fake timers) to await side effects deterministically.

Example for one test:

-  // Wait for useEffect
-  await new Promise(resolve => setTimeout(resolve, 100));
+  await waitFor(() =>
+    expect(
+      document.head.querySelector('link[href*="asciinema-player.css"]')
+    ).toBeInTheDocument()
+  );

Apply similarly to other tests using setTimeout.

Also applies to: 479-489, 503-504, 552-553, 578-579, 600-601, 631-632, 653-654

🤖 Prompt for AI Agents
.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js around lines
451-456 (and similarly at 479-489, 503-504, 552-553, 578-579, 600-601, 631-632,
653-654): tests use an arbitrary await new Promise(resolve => setTimeout(...))
which is flaky; replace those timeouts by using Testing Library’s waitFor
(import it if needed) to wait for the expected DOM changes or effects (e.g.,
await waitFor(() => expect(...).toBeInTheDocument()) or await waitFor(() => /*
check asset load side-effect */)), or alternatively switch the test to use jest
fake timers and advanceTimersByTime/advanceTimersToNextTimer when appropriate;
apply the same replacement pattern to all listed occurrences so waits are
deterministic and faster.

Comment on lines +471 to +495
test('does not duplicate CSS links', async () => {
// First render
render(
<AsciinemaPlayer
src="/fixtures/test-cast.json"
data-testid="player-1"
/>
);
await new Promise(resolve => setTimeout(resolve, 50));

// Second render
render(
<AsciinemaPlayer
src="/fixtures/test-cast.json"
data-testid="player-2"
/>
);
await new Promise(resolve => setTimeout(resolve, 50));

// Should only have one CSS link
const cssLinks = document.head.querySelectorAll(
'link[href*="asciinema-player.css"]'
);
expect(cssLinks).toHaveLength(1);
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Also assert JS bundle is not duplicated.

You verify CSS de-duplication; add a symmetric check for the script tag.

   // Should only have one CSS link
   const cssLinks = document.head.querySelectorAll(
     'link[href*="asciinema-player.css"]'
   );
   expect(cssLinks).toHaveLength(1);
+
+  // Should only have one JS script
+  const jsScripts = document.head.querySelectorAll(
+    'script[src*="asciinema-player.min.js"]'
+  );
+  expect(jsScripts).toHaveLength(1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('does not duplicate CSS links', async () => {
// First render
render(
<AsciinemaPlayer
src="/fixtures/test-cast.json"
data-testid="player-1"
/>
);
await new Promise(resolve => setTimeout(resolve, 50));
// Second render
render(
<AsciinemaPlayer
src="/fixtures/test-cast.json"
data-testid="player-2"
/>
);
await new Promise(resolve => setTimeout(resolve, 50));
// Should only have one CSS link
const cssLinks = document.head.querySelectorAll(
'link[href*="asciinema-player.css"]'
);
expect(cssLinks).toHaveLength(1);
});
test('does not duplicate CSS links', async () => {
// First render
render(
<AsciinemaPlayer
src="/fixtures/test-cast.json"
data-testid="player-1"
/>
);
await new Promise(resolve => setTimeout(resolve, 50));
// Second render
render(
<AsciinemaPlayer
src="/fixtures/test-cast.json"
data-testid="player-2"
/>
);
await new Promise(resolve => setTimeout(resolve, 50));
// Should only have one CSS link
const cssLinks = document.head.querySelectorAll(
'link[href*="asciinema-player.css"]'
);
expect(cssLinks).toHaveLength(1);
// Should only have one JS script
const jsScripts = document.head.querySelectorAll(
'script[src*="asciinema-player.min.js"]'
);
expect(jsScripts).toHaveLength(1);
});
🤖 Prompt for AI Agents
.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js around lines 471
to 495: add a symmetric assertion to ensure the JS bundle isn't duplicated;
after the CSS link check, query document.head for script tags matching the
Asciinema player bundle (e.g. 'script[src*="asciinema-player.js"]' or the exact
bundle name used in the component) and assert the NodeList length is 1 so the
script tag is only injected once.

Comment on lines +136 to +143
expect(links[1]).toHaveAttribute(
'href',
'/features#hosting-integrations'
);
expect(links[2]).toHaveAttribute(
'href',
'/features#continuous-integration'
);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Avoid index-based link assertions to reduce fragility.

Select links by accessible name so DOM order changes don’t break tests.

-      expect(links[1]).toHaveAttribute(
-        'href',
-        '/features#hosting-integrations'
-      );
-      expect(links[2]).toHaveAttribute(
-        'href',
-        '/features#continuous-integration'
-      );
+      expect(
+        screen.getByRole('link', { name: 'Hosting' })
+      ).toHaveAttribute('href', '/features#hosting-integrations');
+      expect(
+        screen.getByRole('link', { name: 'Continuous Integration' })
+      ).toHaveAttribute('href', '/features#continuous-integration');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(links[1]).toHaveAttribute(
'href',
'/features#hosting-integrations'
);
expect(links[2]).toHaveAttribute(
'href',
'/features#continuous-integration'
);
expect(
screen.getByRole('link', { name: 'Hosting' })
).toHaveAttribute('href', '/features#hosting-integrations');
expect(
screen.getByRole('link', { name: 'Continuous Integration' })
).toHaveAttribute('href', '/features#continuous-integration');
🤖 Prompt for AI Agents
.vortex/docs/tests/unit/Card/Card.integration.test.js around lines 136 to 143:
the test currently asserts links by array indices which is fragile; instead
query the links by accessible name (e.g., getByRole('link', { name: /Hosting
Integrations/i }) and getByRole('link', { name: /Continuous Integration/i }) or
use getByText for visible link text) and assert their hrefs, so DOM order
changes won’t break the test; replace the index-based expects with
role/text-based queries that then assert the same href values.

Comment on lines +218 to 221
const nonClickableCard = screen
.getByText('Non-clickable Card')
.closest('div');
expect(nonClickableCard.tagName).toBe('DIV');
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Assert “not linked” more directly.

Check absence of an anchor rather than relying on tagName of a parent.

-      const nonClickableCard = screen
-        .getByText('Non-clickable Card')
-        .closest('div');
-      expect(nonClickableCard.tagName).toBe('DIV');
+      expect(
+        screen.getByText('Non-clickable Card').closest('a')
+      ).toBeNull();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const nonClickableCard = screen
.getByText('Non-clickable Card')
.closest('div');
expect(nonClickableCard.tagName).toBe('DIV');
expect(
screen.getByText('Non-clickable Card').closest('a')
).toBeNull();
🤖 Prompt for AI Agents
.vortex/docs/tests/unit/Card/Card.integration.test.js around lines 218 to 221:
the test currently infers "not linked" by checking the parent element's tagName;
instead assert absence of an anchor element directly. Replace the tagName
assertion with an assertion that there is no <a> descendant for the
"Non-clickable Card" text — e.g., locate the element (or its container) and
assert query for an anchor or role="link" returns null/undefined, ensuring the
test explicitly confirms no link is present.

Comment on lines +697 to 700
expect(
screen.getByText(/Mixed content:.*42.*array values.*nested elements/)
).toBeInTheDocument();
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Make regex assertion more resilient to whitespace/nesting.

getByText with .* can be brittle across nested nodes/newlines. Prefer toHaveTextContent with \s*.

Apply:

-expect(
-  screen.getByText(/Mixed content:.*42.*array values.*nested elements/)
-).toBeInTheDocument();
+expect(document.body).toHaveTextContent(
+  /Mixed content:\s*42\s*array values\s*nested elements/i
+);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(
screen.getByText(/Mixed content:.*42.*array values.*nested elements/)
).toBeInTheDocument();
});
expect(document.body).toHaveTextContent(
/Mixed content:\s*42\s*array values\s*nested elements/i
);
});
🤖 Prompt for AI Agents
In .vortex/docs/tests/unit/VerticalTabs/VerticalTabs.test.js around lines 697 to
700, the current assertion uses screen.getByText with a fragile ".*" regex;
instead, grab the element (e.g. const el = screen.getByText(/Mixed content:/) or
an appropriate query) and assert its text using toHaveTextContent with a
whitespace-tolerant regex like /Mixed content:\s*42\s*array values\s*nested
elements/ so the test tolerates newlines and nested nodes.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1951-ascii-cinema branch from f832e49 to 4736e98 Compare September 2, 2025 23:12
@github-actions github-actions bot temporarily deployed to commit September 2, 2025 23:14 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (5)
.vortex/docs/content/tools/drush.mdx (1)

55-60: Tighten phrasing in SSH key instructions.

Minor grammar/clarity tweaks in comments.

 ```shell
-ssh-add ~/.ssh/path/to/key             # Run on host to run remote Drush commands from host.
+ssh-add ~/.ssh/path/to/key             # On the host: execute remote Drush commands from the host.
 # or
-pygmy addkey --key ~/.ssh/path/to/key  # Run on the host to execute remote Drush commands from inside the container (including ahoy drush).
+pygmy addkey --key ~/.ssh/path/to/key  # On the host: execute remote Drush commands from inside the container (e.g., via "ahoy drush").
.vortex/docs/tests/unit/Card/Card.test.js (2)

356-368: Replace brittle onclick check with explicit “no anchor present” assertion.

React’s synthetic events won’t populate element.onclick; the current check can pass even if click handlers exist. Assert absence of an anchor within the card instead.

Apply:

-      const cardElement = screen.getByText('Non-clickable Card').closest('div');
-
-      // Should not have click handlers
-      expect(cardElement.onclick).toBeFalsy();
+      const wrapper = screen.getByText('Non-clickable Card').closest('.card');
+      // Should not contain a link
+      expect(wrapper.querySelector('a')).toBeNull();

280-283: Prefer userEvent for keyboard focus.

Use user-event to simulate real keyboard nav instead of programmatic focus.

Apply:

-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-      // Should be focusable
-      linkElement.focus();
+      // Should be focusable via keyboard
+      await userEvent.tab();
       expect(document.activeElement).toBe(linkElement);

Also applies to: 2-2

.vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (1)

306-327: Handle nested selector matches for startFromSelector/endAtSelector

Tests currently only cover direct‐child selectors; any nested match inside the container is ignored. Add unit tests for selectors matching deeper nodes (e.g. an <h2> wrapped in a <div>) and update ContentExtractor (src/components/ContentExtractor.js, around lines 57–63 and 74–79) to climb from the matched element up to its direct child under contentDiv before computing the start/end index:

let node = matchElement;
while (node && node.parentElement !== contentDiv) node = node.parentElement;
const index = node ? workingElements.indexOf(node) : -1;
.vortex/docs/content/getting-started/installation.mdx (1)

13-15: Fix article: “in interactive” → “an interactive”.

-We automated the first part by providing an in interactive installer. The second
+We automated the first part by providing an interactive installer. The second
♻️ Duplicate comments (29)
.vortex/docs/.prettierignore (2)

8-8: Confirm intent to ignore MDX.

If intentional, add a brief comment explaining why to avoid future churn; otherwise, drop it so Prettier formats MDX.

Example:

+# MDX formatted by docs toolchain; keep Prettier off.
 *.mdx

9-9: Broaden ignore to nested fixture JSON.

Covers subfolders under tests/fixtures.

-tests/fixtures/*.json
+tests/fixtures/**/*.json
README.md (1)

54-55: Silence MD033 or switch to Markdown image.

Prefer Markdown syntax for lint cleanliness.

Option A (Markdown):

-<img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
+![Vortex installer screenshot](.vortex/docs/static/img/installer.svg)

Option B (keep HTML, disable rule on this line):

+<!-- markdownlint-disable-next-line MD033 -->
 <img src=".vortex/docs/static/img/installer.svg" alt="Vortex installer screenshot" />
.vortex/docs/tests/unit/VerticalTabs/VerticalTabs.test.js (1)

697-699: Make regex assertion resilient to whitespace/nesting.

Prefer toHaveTextContent with a whitespace-tolerant regex to avoid brittle getByText matching across nested nodes/newlines.

Apply:

-expect(
-  screen.getByText(/Mixed content:.*42.*array values.*nested elements/)
-).toBeInTheDocument();
+expect(document.body).toHaveTextContent(
+  /Mixed content:\s*42\s*array values\s*nested elements/i
+);
.vortex/docs/cspell.json (1)

10-10: Add common variants to reduce false positives.

Include frequent terms used in docs/tooling.

   "words": [
     "Asciinema",
+    "asciinema",
+    "asciicast",
+    "svgterm",
.vortex/docs/tests/unit/Card/Card.integration.test.js (2)

132-143: Avoid index-based link assertions; assert by accessible name.

Index-based assertions are fragile to DOM order changes. Use role+name.

Apply:

-      const links = screen.getAllByRole('link');
-      expect(links).toHaveLength(3);
-      expect(links[0]).toHaveAttribute('href', '/features#drupal-foundation');
-      expect(links[1]).toHaveAttribute(
-        'href',
-        '/features#hosting-integrations'
-      );
-      expect(links[2]).toHaveAttribute(
-        'href',
-        '/features#continuous-integration'
-      );
+      const links = screen.getAllByRole('link');
+      expect(links).toHaveLength(3);
+      expect(
+        screen.getByRole('link', { name: 'Drupal' })
+      ).toHaveAttribute('href', '/features#drupal-foundation');
+      expect(
+        screen.getByRole('link', { name: 'Hosting' })
+      ).toHaveAttribute('href', '/features#hosting-integrations');
+      expect(
+        screen.getByRole('link', { name: 'Continuous Integration' })
+      ).toHaveAttribute('href', '/features#continuous-integration');

218-221: Assert “not linked” directly, not via tagName.

Check absence of an anchor rather than parent tagName.

Apply:

-      const nonClickableCard = screen
-        .getByText('Non-clickable Card')
-        .closest('div');
-      expect(nonClickableCard.tagName).toBe('DIV');
+      expect(
+        screen.getByText('Non-clickable Card').closest('a')
+      ).toBeNull();
.vortex/docs/tests/fixtures/test-cast.json (1)

1-17: Asciinema cast is NDJSON, not JSON — rename or ignore to stop formatter/linter errors.

Keep as text: rename to .cast or .jsonl and parse per-line in tests, or add this path to .prettierignore. Current .json triggers Biome parse failures.

Option A (ignore formatters):

# .vortex/docs/.prettierignore
+tests/fixtures/*.json

Option B (rename + load as text in tests):

  • Rename file to test-cast.cast (or .jsonl)
  • Update test loader (example):
import fs from 'node:fs';
const lines = fs.readFileSync(require.resolve('../../fixtures/test-cast.cast'),'utf8')
  .trim().split('\n').map(JSON.parse);
#!/bin/bash
# Verify no JSON imports remain for the cast fixture and that prettier ignore is set.
rg -nP "(import|require).*test-cast\.json" -g ".vortex/docs/**"
test -f .vortex/docs/.prettierignore && rg -n "tests/fixtures/\*\.json" .vortex/docs/.prettierignore || echo ".prettierignore entry missing"
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (2)

31-77: Harden dynamic loader: dedupe scripts, add onerror, async, and clear container before create.

Prevents races, silent failures, and duplicate DOM.

-        if (!window.AsciinemaPlayer) {
-          const script = document.createElement('script');
-          script.src =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
-          script.onload = () => {
-            if (containerRef.current && window.AsciinemaPlayer) {
-              const options = {
-                autoPlay,
-                loop,
-                preload,
-                controls,
-                theme,
-              };
-
-              if (poster) {
-                options.poster = poster;
-              }
-
-              if (startAt !== undefined) {
-                options.startAt = startAt;
-              }
-
-              window.AsciinemaPlayer.create(src, containerRef.current, options);
-            }
-          };
-          document.head.appendChild(script);
-        } else {
+        if (!window.AsciinemaPlayer) {
+          // Reuse in-flight loader or existing script
+          if (!window.__asciinemaLoader) {
+            const existing = document.querySelector('script[src*="asciinema-player.min.js"]');
+            window.__asciinemaLoader = new Promise((resolve, reject) => {
+              const onLoad = () => resolve();
+              const onError = () => reject(new Error('Failed to load Asciinema player'));
+              if (existing) {
+                existing.addEventListener('load', onLoad, { once: true });
+                existing.addEventListener('error', onError, { once: true });
+              } else {
+                const script = document.createElement('script');
+                script.src =
+                  'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.js';
+                script.async = true;
+                script.onload = onLoad;
+                script.onerror = onError;
+                document.head.appendChild(script);
+              }
+            });
+          }
+          try {
+            await window.__asciinemaLoader;
+          } catch (e) {
+            console.error(e);
+            return;
+          }
+        } else {
           if (containerRef.current && window.AsciinemaPlayer) {
-            const options = {
-              autoPlay,
-              loop,
-              preload,
-              controls,
-              theme,
-            };
-
-            if (poster) {
-              options.poster = poster;
-            }
-
-            if (startAt !== undefined) {
-              options.startAt = startAt;
-            }
-
-            window.AsciinemaPlayer.create(src, containerRef.current, options);
+            const options = {
+              autoPlay, loop, preload, controls, theme,
+              ...(poster ? { poster } : {}),
+              ...(startAt !== undefined ? { startAt } : {}),
+            };
+            containerRef.current.innerHTML = '';
+            window.AsciinemaPlayer.create(src, containerRef.current, options);
           }
         }

Also add effect cleanup:

-    loadAsciinemaPlayer();
-  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);
+    loadAsciinemaPlayer();
+    return () => {
+      if (containerRef.current) containerRef.current.innerHTML = '';
+    };
+  }, [src, poster, startAt, autoPlay, loop, preload, controls, theme]);

26-29: Pin AsciinemaPlayer CDN assets to v3.10.0
Replace the floating @3 specifier with the latest stable patch release to prevent unexpected upgrades.

-          link.href =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.css';
+          link.href =
+            'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.css';

-          script.src =
-            'https://cdn.jsdelivr.net/npm/asciinema-player@3/dist/bundle/asciinema-player.min.js';
+          script.src =
+            'https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.js';

Applies to lines 26–29 and 33–35.

.github/workflows/vortex-test-installer.yml (2)

33-36: Use a moving Node range (or LTS) to reduce churn.

Prefer '22.x' (or 'lts/*') over a pinned patch.

-          node-version: 22.17.1
+          node-version: '22.x'

59-64: Make apt installs deterministic and bound script runtime.

Add update, noninteractive, no-recommends, and a timeout for the generator.

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          npm i -g svg-term-cli sharp-cli
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          npm i -g svg-term-cli sharp-cli
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.github/workflows/vortex-release-docs.yml (1)

135-140: Make apt non-interactive and avoid global npm; guard the long-running script.

Use apt-get with update/-y, add a timeout, and prefer npx for tools to avoid global installs.

Apply:

       - name: Generate video for installer (not used in the final artifact)
         run: |
-          sudo apt install asciinema expect
-          npm i -g svg-term-cli sharp-cli
-          ./update-installer-video.sh
+          export DEBIAN_FRONTEND=noninteractive
+          sudo apt-get update -y
+          sudo apt-get install -y --no-install-recommends asciinema expect
+          # Use npx to avoid global installs; pin majors for stability.
+          npx -y svg-term-cli@^3.0.0 --help >/dev/null
+          npx -y sharp-cli@^4.2.0 --help >/dev/null
+          timeout 15m bash ./update-installer-video.sh
         working-directory: .vortex/docs/.utils
.vortex/docs/content/getting-started/installation.mdx (1)

5-5: Wrap AsciinemaPlayer in BrowserOnly to avoid SSR hydration issues.

Prevents access to window/DOM during SSR.

Apply:

+import BrowserOnly from '@docusaurus/BrowserOnly';
-import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
+import AsciinemaPlayer from '@site/src/components/AsciinemaPlayer';
@@
-    <AsciinemaPlayer
-      src="/img/installer.json"
-      poster="npt:0:1"
-      autoPlay={false}
-      loop={false}
-      preload={true}
-      controls={true}
-      theme="asciinema"
-    />
+    <BrowserOnly>
+      {() => (
+        <AsciinemaPlayer
+          src="/img/installer.json"
+          poster="npt:0:1"
+          autoPlay={false}
+          loop={false}
+          preload={true}
+          controls={true}
+          theme="asciinema"
+        />
+      )}
+    </BrowserOnly>

Also applies to: 27-35

.vortex/docs/static/img/installer.json (1)

1-6: Rename cast to .cast or ignore in Biome; update references.

This is asciinema v2 JSON Lines; linters treat .json as invalid.

Option A (preferred):

  • Rename file to .vortex/docs/static/img/installer.cast.
  • Update usages:
-          src="/img/installer.json"
+          src="/img/installer.cast"

Option B:

  • Add a repository-root .biomeignore entry:
/.vortex/docs/static/img/installer.json
#!/bin/bash
# Find references to the old JSON path to update them if you choose Option A.
rg -n --glob '!.git' '/img/installer\.json' -C2
.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js (7)

1-1: Drop unused React import (if using automatic JSX runtime).

Removes noise and unused-import warnings.

- import React from 'react';

16-21: Guard: do not initialize player without src (test).

Replace this smoke test with an assertion that AsciinemaPlayer.create is not called when src is missing.

-  test('renders without src prop', () => {
-    const { container } = render(<AsciinemaPlayer />);
-    expect(container.firstChild).toBeInTheDocument();
-    expect(container.firstChild.tagName).toBe('DIV');
-  });
+  test('does not initialize player without src', async () => {
+    const mockCreate = jest.fn();
+    window.AsciinemaPlayer = { create: mockCreate };
+    const { container } = render(<AsciinemaPlayer />);
+    expect(container.firstChild).toBeInTheDocument();
+    await Promise.resolve();
+    expect(mockCreate).not.toHaveBeenCalled();
+  });

Follow-up (component change outside this file):

- if (containerRef.current && window.AsciinemaPlayer) {
+ if (containerRef.current && window.AsciinemaPlayer && src) {

96-160: Collapse repetitive variant tests with test.each.

Reduces noise and runtime; a single parameterized table covers these boolean/theme/poster/startAt/combo permutations.

-describe('Prop Variants - Boolean Props', () => {
-  test('renders with autoPlay true', () => { ... });
-  ...
-});
+describe('Prop Variants - Boolean Props', () => {
+  test.each([
+    [{ autoPlay: true }], [{ autoPlay: false }],
+    [{ loop: true }], [{ loop: false }],
+    [{ controls: true }], [{ controls: false }],
+    [{ preload: true }], [{ preload: false }],
+  ])('renders with %p', (props) => {
+    const { container } = render(<AsciinemaPlayer src="/fixtures/test-cast.json" {...props} />);
+    expect(container.firstChild).toBeInTheDocument();
+  });
+});

Apply the same pattern to Theme, Poster, StartAt, and Combinations blocks.

Also applies to: 162-208, 210-242, 244-264, 266-359


361-374: Add SSR test: no DOM injection when window is undefined.

Covers the early-return branch.

test('does nothing when window is undefined (SSR)', async () => {
  const realWindow = global.window;
  // @ts-ignore
  delete global.window;
  render(<AsciinemaPlayer src="/fixtures/test-cast.json" />);
  expect(document.head.querySelector('link[href*="asciinema-player.css"]')).toBeNull();
  expect(document.head.querySelector('script[src*="asciinema-player.min.js"]')).toBeNull();
  global.window = realWindow;
});

439-449: Move cleanup to afterEach; keep beforeEach minimal.

Ensures teardown runs even when a test fails.

-describe('Library Integration', () => {
-  beforeEach(() => {
+describe('Library Integration', () => {
+  beforeEach(() => {
     if (typeof window !== 'undefined' && window.AsciinemaPlayer) {
       delete window.AsciinemaPlayer;
     }
     if (typeof document !== 'undefined') {
       document.head.innerHTML = '';
     }
     jest.clearAllMocks();
-  });
+  });
+  afterEach(() => {
+    jest.restoreAllMocks();
+    if (typeof window !== 'undefined' && window.AsciinemaPlayer) {
+      delete window.AsciinemaPlayer;
+    }
+    document.head.innerHTML = '';
+  });

451-456: Replace arbitrary setTimeout waits with waitFor (or fake timers).

Removes flakiness and speeds up tests. Add waitFor import.

- import { render, screen } from '@testing-library/react';
+ import { render, screen, waitFor } from '@testing-library/react';

Example change (apply similarly to other occurrences listed):

- // Wait for useEffect
- await new Promise(resolve => setTimeout(resolve, 100));
+ await waitFor(() =>
+   expect(
+     document.head.querySelector('link[href*="asciinema-player.css"]')
+   ).toBeInTheDocument()
+ );

Also applies to: 479-489, 503-504, 552-553, 578-579, 600-601, 631-632, 653-654


471-495: Also assert JS bundle is not duplicated.

Symmetric to the CSS check.

   const cssLinks = document.head.querySelectorAll(
     'link[href*="asciinema-player.css"]'
   );
   expect(cssLinks).toHaveLength(1);
+  const jsScripts = document.head.querySelectorAll(
+    'script[src*="asciinema-player.min.js"]'
+  );
+  expect(jsScripts).toHaveLength(1);
.vortex/docs/.utils/update-installer-video.sh (7)

50-69: Add node to dependency checks and update install hints.

npx depends on node; also provide CI-friendly hints.

-  local required_commands=("asciinema" "expect" "php" "composer" "npx")
+  local required_commands=("asciinema" "expect" "php" "composer" "node" "npx")
@@
-    echo "  brew install asciinema expect composer  # macOS" >&2
-    echo "  apt-get install asciinema expect-dev composer  # Ubuntu/Debian" >&2
+    echo "  brew install asciinema expect composer node  # macOS" >&2
+    echo "  apt-get install asciinema expect-dev composer nodejs npm  # Ubuntu/Debian" >&2

95-105: Harden composer commands for CI (non-interactive/quiet).

Prevents prompts and reduces log noise.

-    if ! composer install; then
+    if ! composer install --no-interaction --no-ansi --no-progress; then
-    if ! composer build; then
+    if ! composer build -n; then

311-319: Make sed replacement portable and safer.

Handle both /Users and /home paths; keep -i portability.

-  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
-  sed "${sed_opts[@]}" -E 's|/Users/[^/]+/|/home/user/|g' "$output_cast"
+  sed_opts=(-i) && [ "$(uname)" = "Darwin" ] && sed_opts=(-i '')
+  sed "${sed_opts[@]}" -E 's|/(Users|home)/[^/]+/|/home/user/|g' "$output_cast"

328-344: Make all npx invocations non-interactive (-y) for CI.

Avoid stalls on “Need to install…” prompts.

-  if ! npx svg-term --out="$output_svg" <"$output_cast"; then
+  if ! npx -y svg-term --out="$output_svg" <"$output_cast"; then
-  if ! npx svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
+  if ! npx -y svg-term --at=1000 --out="$output_png.svg" <"$output_cast"; then
-  if ! npx sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then
+  if ! npx -y sharp-cli -i "$output_png.svg" -o "$output_png" -f png resize 1280; then

Also applies to: 338-346


184-191: Expect script bug: replace sleep with Tcl after.

sleep isn’t an Expect/Tcl command here; use after with ms conversion.

-proc wait_and_enter {} {
+proc wait_and_enter {} {
     global prompt_delay
     wait_for_quiet 0.25
-    sleep $prompt_delay
+    after [expr {int($prompt_delay * 1000)}]
     safe_send "\r"
 }

423-429: Ensure destination directories exist before copying outputs.

Prevents failures when static dirs are missing.

-  note "JSON cast: $temp_cast → $CAST_FILE"
-  cp "$temp_cast" "$CAST_FILE"
+  note "JSON cast: $temp_cast → $CAST_FILE"
+  mkdir -p "$(dirname "$CAST_FILE")"
+  cp "$temp_cast" "$CAST_FILE"
@@
-  note "SVG image: $temp_svg → $SVG_FILE"
-  cp "$temp_svg" "$SVG_FILE"
+  note "SVG image: $temp_svg → $SVG_FILE"
+  mkdir -p "$(dirname "$SVG_FILE")"
+  cp "$temp_svg" "$SVG_FILE"
@@
-  note "PNG image: $temp_png → $PNG_FILE"
-  cp "$temp_png" "$PNG_FILE"
+  note "PNG image: $temp_png → $PNG_FILE"
+  mkdir -p "$(dirname "$PNG_FILE")"
+  cp "$temp_png" "$PNG_FILE"

12-12: Enable safer bash options.

Add pipefail and nounset is already covered; keeps failures from being masked in pipelines.

-set -eu
+set -euo pipefail
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f832e49 and 4736e98.

⛔ Files ignored due to path filters (2)
  • .vortex/docs/static/img/installer.png is excluded by !**/*.png
  • .vortex/docs/static/img/installer.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • .github/workflows/vortex-release-docs.yml (1 hunks)
  • .github/workflows/vortex-test-installer.yml (2 hunks)
  • .vortex/.ahoy.yml (1 hunks)
  • .vortex/docs/.gitignore (1 hunks)
  • .vortex/docs/.prettierignore (1 hunks)
  • .vortex/docs/.utils/update-installer-video.sh (1 hunks)
  • .vortex/docs/content/contributing/maintenance/release.mdx (1 hunks)
  • .vortex/docs/content/getting-started/installation.mdx (2 hunks)
  • .vortex/docs/content/tools/drush.mdx (1 hunks)
  • .vortex/docs/content/workflows/deployment.mdx (1 hunks)
  • .vortex/docs/cspell.json (1 hunks)
  • .vortex/docs/package.json (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1 hunks)
  • .vortex/docs/src/components/AsciinemaPlayer/index.js (1 hunks)
  • .vortex/docs/static/img/installer.json (1 hunks)
  • .vortex/docs/tests/fixtures/test-cast.json (1 hunks)
  • .vortex/docs/tests/jest-setup.js (1 hunks)
  • .vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js (1 hunks)
  • .vortex/docs/tests/unit/Card/Card.integration.test.js (10 hunks)
  • .vortex/docs/tests/unit/Card/Card.test.js (16 hunks)
  • .vortex/docs/tests/unit/Card/CardGrid.test.js (10 hunks)
  • .vortex/docs/tests/unit/Card/index.test.js (1 hunks)
  • .vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (22 hunks)
  • .vortex/docs/tests/unit/VerticalTabs/MarkerComponents.test.js (2 hunks)
  • .vortex/docs/tests/unit/VerticalTabs/VerticalTabs.test.js (6 hunks)
  • .vortex/docs/tests/unit/VerticalTabs/index.test.js (2 hunks)
  • README.md (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.

Applied to files:

  • .vortex/docs/.gitignore
  • .vortex/docs/.prettierignore
📚 Learning: 2025-06-02T00:41:36.321Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1697
File: .vortex/installer/tests/Fixtures/install/provision_database_lagoon/docs/onboarding.md:16-17
Timestamp: 2025-06-02T00:41:36.321Z
Learning: DO NOT EVER REVIEW FILES IN .vortex/installer/tests/Fixtures/ directory - these are test fixtures and should not be reviewed under any circumstances.

Applied to files:

  • .vortex/docs/.prettierignore
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.

Applied to files:

  • .vortex/docs/.prettierignore
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.

Applied to files:

  • .vortex/docs/.prettierignore
🧬 Code graph analysis (4)
.vortex/docs/tests/unit/VerticalTabs/MarkerComponents.test.js (2)
.vortex/docs/src/components/VerticalTabs/VerticalTab.js (1)
  • VerticalTab (7-10)
.vortex/docs/src/components/VerticalTabs/VerticalTabPanel.js (1)
  • VerticalTabPanel (7-10)
.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js (1)
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js (1)
  • AsciinemaPlayer (3-87)
.vortex/docs/tests/unit/VerticalTabs/index.test.js (3)
.vortex/docs/src/components/VerticalTabs/VerticalTabs.js (1)
  • VerticalTabs (83-210)
.vortex/docs/src/components/VerticalTabs/VerticalTab.js (1)
  • VerticalTab (7-10)
.vortex/docs/src/components/VerticalTabs/VerticalTabPanel.js (1)
  • VerticalTabPanel (7-10)
.vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (1)
.vortex/docs/src/components/ContentExtractor.js (1)
  • ContentExtractor (29-135)
🪛 markdownlint-cli2 (0.17.2)
README.md

54-54: Inline HTML
Element: img

(MD033, no-inline-html)

🪛 LanguageTool
.vortex/docs/content/getting-started/installation.mdx

[grammar] ~5-~5: Use correct spacing
Context: ... '@site/src/components/AsciinemaPlayer'; # Installation The installation process c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 Biome (2.1.2)
.vortex/docs/static/img/installer.json

[error] 1-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-18: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 18-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-24: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 24-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-25: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 25-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-27: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 27-29: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 29-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-31: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 31-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-36: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 36-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-38: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 38-40: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 40-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-44: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 44-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-46: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 46-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-49: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 49-50: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 50-51: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 51-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-56: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 56-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-59: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 59-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-62: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 62-65: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 65-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-68: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 68-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-70: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 70-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-71: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 71-75: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 75-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-77: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 77-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-80: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 80-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-82: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 82-84: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 84-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-86: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 86-89: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 89-91: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 91-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-94: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 94-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-96: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 96-97: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 97-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-100: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 100-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-102: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 102-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-105: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 105-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-107: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 107-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-109: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 109-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-111: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 111-112: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 112-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-114: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 114-116: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 116-120: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 120-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-122: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 122-124: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 124-125: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 125-135: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 135-136: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 136-137: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 137-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-146: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 146-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-150: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 150-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 151-151: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

.vortex/docs/tests/fixtures/test-cast.json

[error] 1-2: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 2-3: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 3-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-6: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 6-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 7-8: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 8-9: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 9-10: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 10-11: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 11-12: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 12-13: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 13-14: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 14-15: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 15-16: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 16-17: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: build (1)
  • GitHub Check: build (0)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-docs
🔇 Additional comments (23)
.vortex/docs/content/workflows/deployment.mdx (1)

32-33: LGTM: spacing fix improves list rendering.

.vortex/docs/.gitignore (1)

24-26: LGTM: clarified intent for installer artifact.

Comment makes it clear this is a file, not a directory.

.vortex/docs/tests/unit/Card/index.test.js (1)

33-33: LGTM: add trailing newline.

Conforms to standard formatting.

.vortex/docs/tests/unit/VerticalTabs/MarkerComponents.test.js (1)

31-34: Whitespace-only cleanup — OK.

Formatting changes look good and keep tests readable.

Also applies to: 65-69, 72-72

.vortex/docs/tests/unit/VerticalTabs/VerticalTabs.test.js (6)

2-8: Reformatted imports — OK.

Multi-line import improves readability without behavior change.


448-450: Readability tweak — OK.

Line-wrapped query/closest chain is fine.


505-508: Assertion formatting — OK.

Expanded expect() args for clarity.


546-549: Readability tweak — OK.

Line-wrapped query/closest chain is fine.


646-649: Assertion formatting — OK.

Expanded expect() args for clarity.


655-658: Assertion formatting — OK.

Expanded expect() args for clarity.

.vortex/docs/tests/jest-setup.js (1)

8-22: Whitespace-only reformat — OK.

No functional changes to the EventTarget polyfill.

.vortex/docs/tests/unit/Card/Card.test.js (1)

20-22: Formatting-only changes look good.

Multiline wrapping and JSX inlining do not alter behavior. Nice consistency.

Also applies to: 129-132, 142-145, 179-186, 191-198, 229-229, 251-254, 340-340, 404-406

.vortex/docs/tests/unit/Card/Card.integration.test.js (1)

46-48: LGTM on reflow/formatting.

No behavioral differences detected.

Also applies to: 67-67, 170-173, 229-229, 251-254, 340-340, 404-406, 417-417

.vortex/docs/tests/unit/VerticalTabs/index.test.js (1)

1-1: Whitespace-only changes — OK.

Exports and identifiers checks remain intact.

Also applies to: 14-20

.vortex/docs/tests/unit/ContentExtractor/ContentExtractor.test.js (1)

10-12: Formatting-only changes — OK.

Readability improved; behavior unchanged.

Also applies to: 68-76, 113-116, 136-139, 156-161, 176-181, 199-205, 222-227, 236-240, 251-257, 270-275, 292-303, 316-321, 344-351, 355-362, 389-401, 414-422, 521-533

.vortex/docs/tests/unit/Card/CardGrid.test.js (2)

61-69: LGTM: inline Card props cleanup keeps behavior unchanged.

Also applies to: 112-113


308-313: LGTM: conditional children coverage is clear and robust.

.vortex/docs/src/components/AsciinemaPlayer/index.js (1)

1-1: LGTM: barrel export is correct.

.vortex/docs/content/contributing/maintenance/release.mdx (1)

17-18: Release steps update reads well; grammar fix applied.

Step 10 addition and Step 11 wording look correct and actionable.

.vortex/docs/.utils/update-installer-video.sh (4)

300-349: Optional: verify cast existence before conversions and propagate errors.

You already check $output_cast; this looks good. No action needed.


351-366: CLI help looks good.

Clear usage and options.


398-421: File presence verification is solid.

Good defensive checks before install step.


50-69: No interactive npx calls without -y detected All npx invocations across the repository include the -y flag, so this concern is resolved.

- name: Generate video for installer (not used in the final artifact)
run: |
sudo apt install asciinema expect
npm i -g svg-term-cli sharp-cli
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Pin CLI tool versions or use npx for reproducibility.

Avoid floating globals; pin versions or run via npx with explicit versions.

-          npm i -g svg-term-cli sharp-cli
+          npm i -g svg-term-cli@<version> sharp-cli@<version>
+          # or:
+          npx -y svg-term-cli@<version> --help >/dev/null
+          npx -y sharp-cli@<version> --help >/dev/null

🌐 Web query:

What are the latest stable versions of svg-term-cli and sharp-cli on npm?

💡 Result:

  • svg-term-cli: 2.1.1 (last published 8 years ago). [1]
  • sharp-cli: 5.2.0 (last published ~2 months ago). [2]

Sources:
[1] npm: svg-term-cli.
[2] npm: sharp-cli.


Pin CLI tool versions for reproducibility.

Use explicit versions rather than floating globals:

-          npm i -g svg-term-cli sharp-cli
+          npm i -g svg-term-cli@2.1.1 sharp-cli@5.2.0
+          # or via npx:
+          npx -y svg-term-cli@2.1.1 --help >/dev/null
+          npx -y sharp-cli@5.2.0 --help >/dev/null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
npm i -g svg-term-cli sharp-cli
npm i -g svg-term-cli@2.1.1 sharp-cli@5.2.0
# or via npx:
npx -y svg-term-cli@2.1.1 --help >/dev/null
npx -y sharp-cli@5.2.0 --help >/dev/null
🤖 Prompt for AI Agents
.github/workflows/vortex-test-installer.yml around line 62: the workflow
installs CLI tools globally with floating versions which harms reproducibility;
update the npm install line to pin explicit versions (e.g.
svg-term-cli@<version> and sharp-cli@<version>) or reference variables/inputs
for those versions, commit the chosen version numbers, and ensure any related
cache or lockfile is updated so CI uses the exact tool versions consistently.

Comment on lines +110 to +115
update-installer-video:
usage: Update installer video in the documentation.
cmd: |
cd docs/.utils
./update-installer-video.sh
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add pre-flight checks before running the updater script.

Fail fast if the script or required tools are missing; keep output actionable.

   update-installer-video:
     usage: Update installer video in the documentation.
     cmd: |
-      cd docs/.utils
-      ./update-installer-video.sh
+      cd docs/.utils
+      test -x ./update-installer-video.sh || { echo "ERROR: docs/.utils/update-installer-video.sh is missing or not executable"; exit 1; }
+      bash -n ./update-installer-video.sh
+      for bin in asciinema expect php composer node; do
+        command -v "$bin" >/dev/null 2>&1 || { echo "ERROR: '$bin' not found in PATH"; exit 1; }
+      done
+      ./update-installer-video.sh

Want me to wire similar checks into the script itself instead?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
update-installer-video:
usage: Update installer video in the documentation.
cmd: |
cd docs/.utils
./update-installer-video.sh
update-installer-video:
usage: Update installer video in the documentation.
cmd: |
cd docs/.utils
test -x ./update-installer-video.sh || { echo "ERROR: docs/.utils/update-installer-video.sh is missing or not executable"; exit 1; }
bash -n ./update-installer-video.sh
for bin in asciinema expect php composer node; do
command -v "$bin" >/dev/null 2>&1 || { echo "ERROR: '$bin' not found in PATH"; exit 1; }
done
./update-installer-video.sh
🤖 Prompt for AI Agents
In .vortex/.ahoy.yml around lines 110 to 115, the updater step runs the script
directly; add pre-flight checks to fail fast if prerequisites are missing by
first verifying the docs/.utils directory exists, that
./update-installer-video.sh is present and executable, and that any required
tools (e.g., bash, git, ffmpeg — list the actual tools your script needs) are
available on PATH; update the cmd block to run these checks and emit clear,
actionable error messages and non-zero exit codes before invoking the script so
the step stops early on missing files or tools.

Comment on lines +17 to +19
# Directory for temporary workspace.
WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: defaulting WORKSPACE_DIR into $HOME and unconditional rm -rf risks data loss.

Create a mktemp workspace when WORKSPACE_DIR is unset, and only delete if we created it.

-# Directory for temporary workspace.
-WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/www/demo}"
+# Directory for temporary workspace (set in main; may be overridden via env).
+# Intentionally not defaulted here to avoid deleting user directories.
+# WORKSPACE_DIR will be initialized in main().
-  info "Creating temporary workspace"
-  WORKSPACE_DIR="${WORKSPACE_DIR:-"$(mktemp -d -t vortex-installer-recording-XXXXXX)"}"
-  [ ! -d "$WORKSPACE_DIR" ] && mkdir -p "$WORKSPACE_DIR"
+  info "Creating temporary workspace"
+  local CREATED_TMPDIR=""
+  if [[ -z "${WORKSPACE_DIR:-}" ]]; then
+    CREATED_TMPDIR="$(mktemp -d -t vortex-installer-recording-XXXXXX)"
+    WORKSPACE_DIR="$CREATED_TMPDIR"
+  else
+    mkdir -p "$WORKSPACE_DIR"
+  fi
@@
-  cleanup() {
-    info "Cleaning up workspace directory"
-    note "Removing: ${WORKSPACE_DIR}"
-    rm -rf "$WORKSPACE_DIR"
-  }
+  cleanup() {
+    if [[ -n "$CREATED_TMPDIR" && "$WORKSPACE_DIR" == "$CREATED_TMPDIR" ]]; then
+      info "Cleaning up workspace directory"
+      note "Removing: ${WORKSPACE_DIR}"
+      rm -rf "$WORKSPACE_DIR"
+    fi
+  }

Also applies to: 377-391

Comment on lines +37 to +41
# Local installer paths
INSTALLER_DOCS="${DOCS_DIR}/static/install"
INSTALLER_BUILD="${PROJECT_ROOT}/.vortex/installer/build/installer.phar"
INSTALLER_SOURCE_DIR="${PROJECT_ROOT}/.vortex/installer"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix installer path mixup: cp directory to file is broken. Introduce DIR/FILE vars and guard.

Currently INSTALLER_DOCS points to a directory but later is copied as a file. Define INSTALLER_DOCS_DIR and INSTALLER_DOCS_FILE, ensure dirs exist, and copy correctly.

-# Local installer paths
-INSTALLER_DOCS="${DOCS_DIR}/static/install"
-INSTALLER_BUILD="${PROJECT_ROOT}/.vortex/installer/build/installer.phar"
+# Local installer paths
+INSTALLER_DOCS_DIR="${DOCS_DIR}/static/install"
+INSTALLER_DOCS_FILE="${INSTALLER_DOCS_DIR}/installer.php"
+INSTALLER_BUILD="${PROJECT_ROOT}/.vortex/installer/build/installer.phar"
-    note "Target: $INSTALLER_DOCS"
-    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+    note "Target: $INSTALLER_DOCS_FILE"
+    mkdir -p "$(dirname "$INSTALLER_DOCS_FILE")"
+    cp "$INSTALLER_BUILD" "$INSTALLER_DOCS_FILE"
-      note "Target: $INSTALLER_DOCS"
-      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS"
+      note "Target: $INSTALLER_DOCS_FILE"
+      mkdir -p "$(dirname "$INSTALLER_DOCS_FILE")"
+      cp "$INSTALLER_BUILD" "$INSTALLER_DOCS_FILE"
-  note "Source: $INSTALLER_DOCS"
-  note "Target: $workspace_dir/installer.php"
-  cp "$INSTALLER_DOCS" "$workspace_dir/installer.php"
+  note "Source: $INSTALLER_DOCS_FILE"
+  note "Target: $workspace_dir/installer.php"
+  if [ ! -f "$INSTALLER_DOCS_FILE" ]; then
+    fail "Installer not found at: $INSTALLER_DOCS_FILE"
+    return 1
+  fi
+  cp "$INSTALLER_DOCS_FILE" "$workspace_dir/installer.php"
-  note "  - Static: $INSTALLER_DOCS"
+  note "  - Static: $INSTALLER_DOCS_FILE"

Also applies to: 76-83, 111-120, 286-290, 129-131

🤖 Prompt for AI Agents
.vortex/docs/.utils/update-installer-video.sh around lines 37-41: INSTALLER_DOCS
is currently a path used inconsistently as a directory in some places and a file
in others; define explicit INSTALLER_DOCS_DIR and INSTALLER_DOCS_FILE (or
INSTALLER_BUILD_FILE) variables, ensure target directories exist with mkdir -p,
guard copies with test -f for files and test -d for directories, and use cp with
the correct source->destination semantics (file to file, directory to
directory). Apply the same pattern and guards to the other affected ranges
(lines 76-83, 111-120, 129-131, 286-290): introduce DIR/FILE vars, create any
needed dirs, check file existence before copying, and copy to the correct
destination type.

Comment on lines +21 to +22
"lint-js": "eslint src/ tests/ && prettier --check src/ tests/",
"lint-js-fix": "eslint src/ tests/ --fix && prettier --write src/ tests/",
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Including tests in lint is good; ensure ESLint knows about Jest.

Without Jest/Test Library rules or env, linting tests may produce false positives.

  • Consider switching non-mutating lint to use lint-docs (not lint-docs-fix).
  • Add Jest and Testing Library ESLint config for tests.

Apply:

-    "lint": "yarn lint-js && yarn lint-docs-fix",
+    "lint": "yarn lint-js && yarn lint-docs",
-    "lint-js": "eslint src/ tests/ && prettier --check src/ tests/",
-    "lint-js-fix": "eslint src/ tests/ --fix && prettier --write src/ tests/",
+    "lint-js": "eslint src/ tests/ --max-warnings=0 && prettier --check src/ tests/",
+    "lint-js-fix": "eslint src/ tests/ --fix && prettier --write src/ tests/",

Add (in your ESLint config):

{
  "overrides": [
    {
      "files": ["**/tests/**/*.{js,jsx,ts,tsx}"],
      "env": { "jest": true },
      "plugins": ["testing-library", "jest"],
      "extends": ["plugin:testing-library/react", "plugin:jest/recommended"]
    }
  ]
}

If needed, add devDependencies:

  • eslint-plugin-jest
  • eslint-plugin-testing-library

Verification script:


🏁 Script executed:

#!/bin/bash
# Check ESLint config for Jest/testing-library overrides
fd -H -a ".eslintrc*" . | while read -r f; do
  echo "== $f =="; sed -n '1,200p' "$f"
done

# Grep for jest env in any ESLint config
rg -nP '"jest"\s*:\s*true' -S || echo "No explicit jest env found."

Length of output: 6319


🏁 Script executed:

#!/bin/bash
# Display the scripts section in .vortex/docs/package.json
sed -n '1,200p' .vortex/docs/package.json

Length of output: 2417


Tighten lint scripts and add ESLint plugins for tests

  • In .vortex/docs/package.json
    • Change "lint" from
    "lint": "yarn lint-js && yarn lint-docs-fix"
    to
    "lint": "yarn lint-js && yarn lint-docs"
    • Update "lint-js" to fail on warnings:
    - "lint-js": "eslint src/ tests/ && prettier --check src/ tests/"
    + "lint-js": "eslint src/ tests/ --max-warnings=0 && prettier --check src/ tests/"
    (keep "lint-js-fix" unchanged)
  • In .vortex/docs/.eslintrc.js, add an overrides block for your tests:
    overrides: [
      {
        files: ["**/tests/**/*.{js,jsx,ts,tsx}"],
        plugins: ["jest", "testing-library"],
        extends: ["plugin:jest/recommended", "plugin:testing-library/react"]
      }
    ]
  • Add these to devDependencies in .vortex/docs/package.json:
    eslint-plugin-jest
    eslint-plugin-testing-library
    

Comment on lines +37 to +52
const options = {
autoPlay,
loop,
preload,
controls,
theme,
};

if (poster) {
options.poster = poster;
}

if (startAt !== undefined) {
options.startAt = startAt;
}

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Deduplicate options building.

Build once with conditional spreads; less drift.

-              const options = {
-                autoPlay,
-                loop,
-                preload,
-                controls,
-                theme,
-              };
-              if (poster) {
-                options.poster = poster;
-              }
-              if (startAt !== undefined) {
-                options.startAt = startAt;
-              }
+              const options = {
+                autoPlay, loop, preload, controls, theme,
+                ...(poster ? { poster } : {}),
+                ...(startAt !== undefined ? { startAt } : {}),
+              };

(and same in the already-loaded branch)

Also applies to: 59-76

🤖 Prompt for AI Agents
.vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js around lines 37
to 52: the options object is built twice with conditional assignments (poster,
startAt); consolidate into a single options construction using conditional
spreads so the base properties (autoPlay, loop, preload, controls, theme) are
set once and poster/startAt are merged only when present; apply the same
refactor to the already-loaded branch around lines 59 to 76 to remove duplicated
logic and keep a single source of truth for options.

Comment on lines +375 to +394
test('renders with undefined props', () => {
const { container } = render(
<AsciinemaPlayer
src={undefined}
poster={undefined}
startAt={undefined}
theme={undefined}
/>
);

expect(container.firstChild).toBeInTheDocument();
});

test('renders with empty string props', () => {
const { container } = render(
<AsciinemaPlayer src="" poster="" theme="" className="" />
);

expect(container.firstChild).toBeInTheDocument();
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Edge-cases: assert create is not called for falsy/empty src.

Tightens behavior for '', null, undefined inputs.

test.each([undefined, null, ''])('does not initialize player when src=%p', async (badSrc) => {
  const mockCreate = jest.fn();
  window.AsciinemaPlayer = { create: mockCreate };
  render(<AsciinemaPlayer src={badSrc} />);
  await Promise.resolve();
  expect(mockCreate).not.toHaveBeenCalled();
});
🤖 Prompt for AI Agents
.vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js around lines 375
to 394: add a parameterized test that verifies the player is not initialized for
falsy/empty src values by creating a jest.fn() mock for
window.AsciinemaPlayer.create, assigning it to window.AsciinemaPlayer, rendering
<AsciinemaPlayer src={badSrc} /> for each of [undefined, null, ''], awaiting a
microtask (e.g. await Promise.resolve()) to let any effects run, and asserting
mockCreate was not called; ensure you restore or delete window.AsciinemaPlayer
after the test to avoid leaking globals.

Comment on lines +618 to +645
test('handles errors during library loading', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

// Force an error by making querySelector throw
const originalQuerySelector = document.querySelector;
document.querySelector = jest.fn().mockImplementation(() => {
throw new Error('Mock error');
});

const { container } = render(
<AsciinemaPlayer src="/fixtures/test-cast.json" />
);

await new Promise(resolve => setTimeout(resolve, 50));

// Should still render the container
expect(container.firstChild).toBeInTheDocument();

// Should log the error
expect(consoleSpy).toHaveBeenCalledWith(
'Failed to load Asciinema player:',
expect.any(Error)
);

// Restore
document.querySelector = originalQuerySelector;
consoleSpy.mockRestore();
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Ensure document.querySelector override is restored on failure.

Wrap in try/finally to avoid leaking patched globals if an assertion fails.

-  const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
-  const originalQuerySelector = document.querySelector;
-  document.querySelector = jest.fn().mockImplementation(() => { throw new Error('Mock error'); });
-  const { container } = render(<AsciinemaPlayer src="/fixtures/test-cast.json" />);
-  await new Promise(resolve => setTimeout(resolve, 50));
-  expect(container.firstChild).toBeInTheDocument();
-  expect(consoleSpy).toHaveBeenCalledWith('Failed to load Asciinema player:', expect.any(Error));
-  document.querySelector = originalQuerySelector;
-  consoleSpy.mockRestore();
+  const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
+  const originalQuerySelector = document.querySelector;
+  try {
+    document.querySelector = jest.fn().mockImplementation(() => { throw new Error('Mock error'); });
+    const { container } = render(<AsciinemaPlayer src="/fixtures/test-cast.json" />);
+    await waitFor(() => expect(container.firstChild).toBeInTheDocument());
+    expect(consoleSpy).toHaveBeenCalledWith('Failed to load Asciinema player:', expect.any(Error));
+  } finally {
+    document.querySelector = originalQuerySelector;
+    consoleSpy.mockRestore();
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .vortex/docs/tests/unit/AsciinemaPlayer/AsciinemaPlayer.test.js around lines
618 to 645, the test overrides document.querySelector and mocks console.error
but restores them only after assertions, so a failing assertion can leak patched
globals; wrap the body of the test in a try/finally and in the finally block
restore document.querySelector to the originalQuerySelector and call
consoleSpy.mockRestore() (and any other cleanup) to guarantee restoration even
if the test throws.

Comment on lines +221 to 229
const { container } = render(<CardGrid>{null}</CardGrid>);

const gridElement = container.querySelector('.cards-grid');
expect(gridElement).toBeInTheDocument();
});

test('handles undefined children', () => {
const { container } = render(
<CardGrid>
{undefined}
</CardGrid>
);
const { container } = render(<CardGrid>{undefined}</CardGrid>);

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Strengthen “null/undefined children” assertions.

Also assert there are zero element children to prove no stray nodes rendered.

-const { container } = render(<CardGrid>{null}</CardGrid>);
+const { container } = render(<CardGrid>{null}</CardGrid>);
 const gridElement = container.querySelector('.cards-grid');
 expect(gridElement).toBeInTheDocument();
+expect(gridElement.children.length).toBe(0);

And similarly for the undefined case:

-const { container } = render(<CardGrid>{undefined}</CardGrid>);
+const { container } = render(<CardGrid>{undefined}</CardGrid>);
 const gridElement = container.querySelector('.cards-grid');
 expect(gridElement).toBeInTheDocument();
+expect(gridElement.children.length).toBe(0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { container } = render(<CardGrid>{null}</CardGrid>);
const gridElement = container.querySelector('.cards-grid');
expect(gridElement).toBeInTheDocument();
});
test('handles undefined children', () => {
const { container } = render(
<CardGrid>
{undefined}
</CardGrid>
);
const { container } = render(<CardGrid>{undefined}</CardGrid>);
const { container } = render(<CardGrid>{null}</CardGrid>);
const gridElement = container.querySelector('.cards-grid');
expect(gridElement).toBeInTheDocument();
expect(gridElement.children.length).toBe(0);
});
test('handles undefined children', () => {
const { container } = render(<CardGrid>{undefined}</CardGrid>);
const gridElement = container.querySelector('.cards-grid');
expect(gridElement).toBeInTheDocument();
expect(gridElement.children.length).toBe(0);
});
🤖 Prompt for AI Agents
In .vortex/docs/tests/unit/Card/CardGrid.test.js around lines 221 to 229, the
tests for null and undefined children only assert the grid exists but do not
verify that no element children were rendered; update both tests to additionally
assert that the grid has zero element children (e.g., check
gridElement.children.length === 0 or use querySelectorAll(':scope > *').length
=== 0) to prove no stray nodes are present.

Comment on lines +451 to 453
{''} {'\n'}
<Card icon="✅" title="Valid Card" description="Real content" />
{' '}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Optional: assert whitespace-only children don’t affect element count.

Add a children-length assertion to make the intent explicit.

 render(
   <CardGrid>
     {''} {'\n'}
     <Card icon="✅" title="Valid Card" description="Real content" />
     {'   '}
   </CardGrid>
 );
+const grid = document.querySelector('.cards-grid');
+expect(grid.children.length).toBe(1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{''} {'\n'}
<Card icon="✅" title="Valid Card" description="Real content" />
{' '}
render(
<CardGrid>
{''} {'\n'}
<Card icon="✅" title="Valid Card" description="Real content" />
{' '}
</CardGrid>
);
const grid = document.querySelector('.cards-grid');
expect(grid.children.length).toBe(1);
🤖 Prompt for AI Agents
In .vortex/docs/tests/unit/Card/CardGrid.test.js around lines 451-453, the test
renders whitespace-only children around a Card but doesn't explicitly assert
that those whitespace-only children don't change the element count; add an
explicit assertion after rendering that the number of Card components (or
rendered card elements) equals the expected count (e.g., 1) to make the intent
explicit, using the same test wrapper/query method already in the file (e.g.,
wrapper.find/Card query or getAllByRole/getAllByTestId) so the test fails if
whitespace nodes are incorrectly counted.

@AlexSkrypnyk AlexSkrypnyk merged commit 17526a4 into develop Sep 3, 2025
31 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/1951-ascii-cinema branch September 3, 2025 00:24
@github-project-automation github-project-automation bot moved this from BACKLOG to Release queue in Vortex Sep 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[DOCS] Use svg to embed installer asciicinema

2 participants