Skip to content

feat: refactor cheat sheet to Livewire command index with grouped sections - #30

Merged
loadinglucian merged 8 commits into
mainfrom
feat/cheat-sheet-livewire-sections
Feb 20, 2026
Merged

feat: refactor cheat sheet to Livewire command index with grouped sections#30
loadinglucian merged 8 commits into
mainfrom
feat/cheat-sheet-livewire-sections

Conversation

@loadinglucian

Copy link
Copy Markdown
Owner

Summary

  • Refactors the cheat sheet page into a Livewire CommandIndex component with grouped sections, matching the docs site architecture
  • Introduces a file-backed DocsOutputCacheService for caching rendered output to disk
  • Adds comprehensive fixture-based test suite covering the new component and cache service
  • Cleans up stale files: HomeController, welcome.blade.php, old non-Livewire command-index.blade.php
  • Fixes x-slot closing tag syntax and removes the redundant prefix info bar from the command index view

Test plan

  • Run php artisan test --compact tests/Feature/ to confirm all feature tests pass
  • Visit /command-index and verify commands render in grouped sections
  • Confirm cache writes/reads correctly by checking the cache directory

Convert the cheat sheet from a closure route to a full Livewire
component with cached data. Group commands into semantic sections
(Server & Site Operations, Data Services, Cloud Providers, etc.)
for better discoverability.

Extract shared header and footer into reusable Blade components,
replace target="_blank" with wire:navigate on the TOC cheat sheet
link, improve code block styling, and add codex to boost config.
Renames the CheatSheet feature to CommandIndex across the entire
codebase—Livewire component, service, views, layout, route, config
key, env var, sidebar TOC link, and tests.
Replaces the Laravel Cache facade approach with a dedicated
file-backed cache service that integrates with optimize:clear.

- Add DocsOutputCacheService with SHA1-keyed JSON files, atomic
  writes via temp-rename, and flock-based double-checked locking
- Add DocsClearCommand registered via AppServiceProvider::optimizes()
  so optimize:clear wipes the docs output cache automatically
- Refactor DocsViewer and CommandIndex to cache full page payloads
  (including TOC) through DocsOutputCacheService
- Add DocumentService file fingerprinting (mtime+size) for
  per-document cache invalidation without TTLs
- Replace command_index.cache_ttl_seconds config with unified
  docs.cache.* config block
- Add optimize:clear before optimize in deploy script
Replaces real-docs-dependent tests with isolated fixture-based tests
using a shared UsesDocsFixtures concern. Adds full unit coverage for
all services (DocumentService, MarkdownService, HeadingExtractorService,
CommandIndexService, DocsOutputCacheService, TocParserService) and a
Livewire DocsViewer test. Removes superseded feature-level cache tests.
Simplifies Pest.php and ArchTest to match actual project structure.
- Remove HomeController (broken redirect to self) and welcome.blade.php
- Delete old non-Livewire command-index view
- Fix x-slot closing tag in Livewire command-index view
- Remove redundant prefix info bar from command-index view
- Add PHPDoc array type annotation to DocsOutputCacheService::key()
@coderabbitai

coderabbitai Bot commented Feb 20, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Replaces the static cheat-sheet with a cached, dynamic Command Index and Livewire component; introduces a file-backed DocsOutputCacheService, integrates caching into DocumentService and DocsViewer, registers a docs:clear command, refactors views/layouts into reusable components, and adds fixture-driven tests and test helpers.

Changes

Cohort / File(s) Summary
Caching Infrastructure
app/Services/DocsOutputCacheService.php, config/docs.php
Adds a file-based, versioned docs output cache with locking, atomic writes, remember/get/put/clear APIs; replaces old cheat_sheet TTL with docs.cache keys (enabled, path, version, lock_timeout_seconds).
Command Index & Routes
app/Services/CommandIndexService.php, app/Livewire/CommandIndex.php, routes/web.php
Renames/rewrites CommandCheatSheetService → CommandIndexService with sections/groups model; new CommandIndex Livewire component and route /command-index (removed /cheat-sheet).
Docs Viewer & Document Service
app/Livewire/DocsViewer.php, app/Services/DocumentService.php
DocsViewer refactored to payload-driven hydration using DocsOutputCacheService; DocumentService uses DocsOutputCacheService with fingerprint-based caching and loadFromPath helper.
Service Registration & CLI
app/Providers/AppServiceProvider.php, app/Console/Commands/DocsClearCommand.php
Registers DocsOutputCacheService singleton and adds optimize/clear wiring; adds docs:clear artisan command to clear docs cache.
Views & Layouts
resources/views/components/docs/header.blade.php, resources/views/components/docs/footer.blade.php, resources/views/components/layouts/command-index.blade.php, resources/views/components/layouts/docs.blade.php, resources/views/components/docs/toc.blade.php
Extracts header/footer components, adds command-index layout, updates TOC to include Command Index, and replaces inline header/footer markup with components.
Removed Templates & Controller
resources/views/cheat-sheet.blade.php, resources/views/welcome.blade.php, app/Http/Controllers/HomeController.php
Deletes the legacy cheat-sheet view, welcome page, and HomeController.
Livewire Views
resources/views/livewire/command-index.blade.php
New Livewire Blade view rendering sections, groups, commands, counts, and TOC.
Styling & Assets
resources/css/app.css, resources/css/highlight.css
Adjusts prose pre background opacity and line-height; tweaks light-mode comment/quote/deletion color.
IDE/Meta & Deploy
.phpstorm.meta.php, boost.json, .deployer/scripts/deploy.sh
Renames metadata keys from cheat-sheet → command-index, adds "codex" to boost.json arrays, and inserts artisan optimize:clear step in deploy script.
Tests — Fixtures & Helpers
tests/Concerns/UsesDocsFixtures.php, tests/Fixtures/docs/*
Adds UsesDocsFixtures trait and multiple docs fixtures (README, docs pages, fake command classes) to enable deterministic tests.
Tests — Feature
tests/Feature/CommandIndexPageTest.php, tests/Feature/DocsClearCommandTest.php, tests/Feature/DocsRoutesSmokeTest.php, tests/Feature/DocsSidebarNavigationTest.php
Adds feature tests for command index rendering, docs:clear behavior, routes smoke tests, and sidebar navigation; removes old CheatSheetPageTest and MarkdownServiceTest.
Tests — Unit
tests/Unit/DocsOutputCacheServiceTest.php, tests/Unit/DocumentServiceTest.php, tests/Unit/CommandIndexServiceTest.php, tests/Unit/Livewire/DocsViewerTest.php, tests/Unit/MarkdownServiceTest.php, tests/Unit/TocParserServiceTest.php, tests/Unit/HeadingExtractorServiceTest.php
Comprehensive unit tests for caching, document loading, command index grouping/sections, Livewire hydration, markdown conversion, and TOC parsing.
Test Bootstrapping & Architecture
tests/Pest.php, tests/Arch/ArchTest.php
Simplifies Pest bootstrap (removes global expectations/helpers); reduces Arch tests to focused public-API checks for controllers, Livewire, services, repositories, and models.
Removed Icons
resources/views/flux/icon/square-arrow-out-up-right.blade.php
Deletes an unused Flux icon blade component.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: refactoring the cheat sheet into a Livewire command index with grouped sections, which is the core objective of this PR.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of major changes including the new Livewire component, caching service, test suite additions, and file removals that align with the file modifications.

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


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
tests/Unit/MarkdownServiceTest.php (1)

93-108: Consider a more stable assertion for alert rendering.

Asserting against the specific Tailwind class dark:border-l-amber-400 couples this test to the component's styling implementation. If the alert component's styling is updated, this test will fail even though the behavior is correct.

Consider asserting against a more stable marker, such as a semantic class name, role attribute, or data- attribute if available in the component.

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

In `@tests/Unit/MarkdownServiceTest.php` around lines 93 - 108, The test
"it('renders important alerts with block content')" couples behavior to a
Tailwind class; update the assertion to check for a stable semantic marker
instead of 'dark:border-l-amber-400' — for example assert that the generated
HTML from app(MarkdownService::class)->toHtml($markdown) contains a role or data
attribute like role="alert" or data-alert="important" (or add such an attribute
in the alert rendering code if missing) and keep the existing checks for the
list items; reference the test block name and the MarkdownService::toHtml call
to locate where to change the assertion.
tests/Fixtures/docs/docs/link-behavior.md (1)

7-7: Invalid anchor reference may cause test ambiguity.

The anchor #local-anchor doesn't correspond to any heading in this document. If this is intentional to test invalid anchor handling, consider adding a comment to clarify. Otherwise, add a heading like ## Local Anchor to make the link valid.

Option 1: Add a valid anchor target
 [Anchor](`#local-anchor`)
 [External](https://example.com/docs)
+
+## Local Anchor
+
+This section provides the target for the anchor link above.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Fixtures/docs/docs/link-behavior.md` at line 7, The link target
`#local-anchor` in tests/Fixtures/docs/docs/link-behavior.md points to no
heading and creates ambiguity; either add a matching heading (e.g., insert `##
Local Anchor` somewhere in the document to create the `#local-anchor` target) or
explicitly mark the link as intentionally invalid with a short comment near the
link explaining it tests invalid-anchor behavior so readers know it's
deliberate.
resources/views/components/docs/footer.blade.php (1)

21-23: Consider using Flux icon for consistency.

The inline SVG heart icon could be replaced with a Flux icon component for consistency with the other icons in this file (flux:icon.github, flux:icon.reddit, flux:icon.x). However, this is a minor style preference.

Optional: Use flux:icon.heart if available
         <span>Made with tender love</span>
-        <svg class="inline-flex shrink-0 text-zinc-400 [:where(&amp;)]:size-4" data-flux-icon="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
-            <path d="M2 6.342a3.375 3.375 0 0 1 6-2.088 3.375 3.375 0 0 1 5.997 2.26c-.063 2.134-1.618 3.76-2.955 4.784a14.437 14.437 0 0 1-2.676 1.61c-.02.01-.038.017-.05.022l-.014.006-.004.002h-.002a.75.75 0 0 1-.592.001h-.002l-.004-.003-.015-.006a5.528 5.528 0 0 1-.232-.107 14.395 14.395 0 0 1-2.535-1.557C3.564 10.22 1.999 8.558 1.999 6.38L2 6.342Z"></path>
-        </svg>
+        <flux:icon.heart class="inline-flex size-4 shrink-0 text-zinc-400" />
         <span>and care</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/views/components/docs/footer.blade.php` around lines 21 - 23,
Replace the inline SVG heart markup with the project's Flux icon component
(e.g., flux:icon.heart or the x-flux component variant) so it matches the other
icons like flux:icon.github, flux:icon.reddit and flux:icon.x; preserve the
existing attributes and classes (class="inline-flex shrink-0 text-zinc-400
[:where(&amp;)]:size-4", data-flux-icon, view/accessibility attrs such as
aria-hidden and data-slot) when instantiating the Flux icon component so visual
styling and a11y remain identical.
tests/Feature/DocsClearCommandTest.php (1)

8-27: Consider cleaning up the test cache directory after the test.

The test creates a unique cache directory under storage/framework/cache/docs-output-tests/ but doesn't clean it up after completion. Over time, repeated test runs could accumulate orphaned directories.

♻️ Add afterEach cleanup
afterEach(function (): void {
    $cachePath = config('docs.cache.path');
    if ($cachePath && File::isDirectory($cachePath)) {
        File::deleteDirectory($cachePath);
    }
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Feature/DocsClearCommandTest.php` around lines 8 - 27, The test leaves
a per-run cache directory under storage/framework/cache/docs-output-tests/
(created in the it(...) test using DocsOutputCacheService::class and
config()->set('docs.cache.path', ...)), so add an afterEach teardown that reads
the configured cache path via config('docs.cache.path') and, if it exists and
File::isDirectory(...) is true, removes it with File::deleteDirectory(...);
place this afterEach alongside the test so the unique test cache is cleaned up
after each run.
app/Services/DocsOutputCacheService.php (1)

113-140: Legitimate null values cannot be cached and will trigger re-computation on every call.

In remember(), returning null from the resolver bypasses caching (Line 134), and a cached null will be treated as a cache miss (Line 121). If any resolver legitimately returns null, this creates unnecessary overhead.

Consider documenting this behavior or using a sentinel value/wrapper if null is a valid cached value.

📝 Document the null behavior in the method docblock
+    /**
+     * Get or compute and cache a value.
+     *
+     * Note: Null values are not cached. If the resolver returns null,
+     * it will be called again on subsequent requests.
+     */
     public function remember(string $key, callable $callback): mixed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/DocsOutputCacheService.php` around lines 113 - 140, The
remember() method treats null as a cache-miss and thus re-computes resolvers
that legitimately return null; update remember (and/or get/put) to support
caching explicit nulls by using a sentinel wrapper (e.g., wrap values in a
CacheValue object or store a special marker string/array) when storing and
unwrapping on retrieval so null is distinguishable from "not present", or
alternatively store an additional boolean flag indicating presence; update
withLock, get, and put usages in DocsOutputCacheService::remember to wrap/unwrap
values atomically, and add a docblock on remember() explaining that nulls are
now supported via the sentinel/wrapper and how callers should interpret returned
values.
app/Services/CommandIndexService.php (1)

198-207: Edge case: commands without a colon return the full command name as namespace.

When a command has no colon separator (e.g., deploy), explode(':', $command, 2) returns ['deploy'], and $segments[0] returns 'deploy' instead of 'general'. This may be intentional, but consider if standalone commands should be grouped under 'general' or their own name.

♻️ Optional: group standalone commands under 'general'
 private function extractNamespace(string $command): string
 {
     $segments = explode(':', $command, 2);

-    if ($segments[0] === '') {
+    if ($segments[0] === '' || count($segments) === 1) {
         return 'general';
     }

     return $segments[0];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/CommandIndexService.php` around lines 198 - 207, The
extractNamespace method currently returns the whole command when there is no
colon (e.g., "deploy"); update extractNamespace(string $command) to treat
standalone commands as belonging to the "general" namespace by checking for the
presence of a colon (e.g., use strpos($command, ':') === false or
count(explode(...)) < 2) and return 'general' in that case, otherwise keep the
existing logic that returns the segment before the colon.
resources/views/components/docs/toc.blade.php (1)

17-25: Consider removing the external link icon for an internal route.

The square-arrow-out-up-right icon typically indicates navigation to an external site. Since /command-index is an internal route within the same application, this icon may mislead users into thinking they're leaving the docs site.

♻️ Option: Remove the icon or use a different one
         <flux:navlist.item href="{{ route('command-index') }}" wire:navigate :current="$currentPath === 'command-index'">
-            <div class="flex items-center gap-2">
-                <span>Command Index</span>
-
-                <flux:icon.square-arrow-out-up-right class="size-3" />
-            </div>
+            Command Index
         </flux:navlist.item>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/views/components/docs/toc.blade.php` around lines 17 - 25, The
external-link icon (flux:icon.square-arrow-out-up-right) is misleading for the
internal route rendered by flux:navlist.item (href="{{ route('command-index')
}}"); remove the flux:icon.square-arrow-out-up-right element from the Command
Index nav item or replace it with an internal/navigation-appropriate icon (e.g.,
flux:icon.chevron-right or a simple caret) so the visual affordance matches
internal navigation; update the markup inside the flux:navlist.item's div (the
element containing <span>Command Index</span>) accordingly.
tests/Unit/DocumentServiceTest.php (1)

61-86: Consider extracting fingerprint logic to avoid test brittleness.

The test manually replicates the fingerprint computation from DocumentService::fingerprint(). If the fingerprint format changes, this test will silently break. Consider either making fingerprint() protected/package-visible for testing, or accepting this coupling as intentional for integration testing.

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

In `@tests/Unit/DocumentServiceTest.php` around lines 61 - 86, The test duplicates
the fingerprint computation from DocumentService::fingerprint(), which makes it
brittle; change the test to call the service's fingerprint logic instead of
reimplementing it (e.g., expose fingerprint() for test usage or add a
package-visible/protected accessor on DocumentService and call that in the test)
so the cache key uses DocumentService::fingerprint() directly when building the
cache key for DocsOutputCacheService::key('document', [...]); update the test to
retrieve the fingerprint from the service and use that value when putting the
cached payload.
app/Livewire/DocsViewer.php (1)

93-110: Consider redirect status code for missing content.

The 301 (permanent) redirects for missing README or pages are browser-cacheable, meaning users won't re-check the server for those URLs. If documentation pages might be added later under previously-missing slugs, consider using 302 (temporary) or 307 redirects instead.

📋 If temporary redirects are preferred
             if ($readme === null) {
-                throw new HttpResponseException(new RedirectResponse('/', 301));
+                throw new HttpResponseException(new RedirectResponse('/', 302));
             }
         if ($document === null) {
-            throw new HttpResponseException(new RedirectResponse('/', 301));
+            throw new HttpResponseException(new RedirectResponse('/', 302));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Livewire/DocsViewer.php` around lines 93 - 110, The redirects for missing
content in DocsViewer currently use RedirectResponse with a 301 status (see the
HttpResponseException wrapping RedirectResponse when $readme === null and when
$document === null); change those to a non-permanent redirect (e.g., use status
302 or 307) so browsers won’t cache the missing-slug redirect permanently—update
both RedirectResponse(...) instantiations in the class/method that handles
$readme and $document checks to use the chosen temporary status code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@resources/views/components/docs/header.blade.php`:
- Line 19: The external GitHub link rendered by the flux:button component
currently opens with target="_blank" but lacks rel="noopener"; update the
flux:button invocation (the element with icon="github", size="sm",
href="https://github.com/loadinglucian/deployer-php/") to include rel="noopener"
(or rel="noopener noreferrer") so the new tab cannot access window.opener and to
mitigate tab-nabbing attacks.

---

Nitpick comments:
In `@app/Livewire/DocsViewer.php`:
- Around line 93-110: The redirects for missing content in DocsViewer currently
use RedirectResponse with a 301 status (see the HttpResponseException wrapping
RedirectResponse when $readme === null and when $document === null); change
those to a non-permanent redirect (e.g., use status 302 or 307) so browsers
won’t cache the missing-slug redirect permanently—update both
RedirectResponse(...) instantiations in the class/method that handles $readme
and $document checks to use the chosen temporary status code.

In `@app/Services/CommandIndexService.php`:
- Around line 198-207: The extractNamespace method currently returns the whole
command when there is no colon (e.g., "deploy"); update extractNamespace(string
$command) to treat standalone commands as belonging to the "general" namespace
by checking for the presence of a colon (e.g., use strpos($command, ':') ===
false or count(explode(...)) < 2) and return 'general' in that case, otherwise
keep the existing logic that returns the segment before the colon.

In `@app/Services/DocsOutputCacheService.php`:
- Around line 113-140: The remember() method treats null as a cache-miss and
thus re-computes resolvers that legitimately return null; update remember
(and/or get/put) to support caching explicit nulls by using a sentinel wrapper
(e.g., wrap values in a CacheValue object or store a special marker
string/array) when storing and unwrapping on retrieval so null is
distinguishable from "not present", or alternatively store an additional boolean
flag indicating presence; update withLock, get, and put usages in
DocsOutputCacheService::remember to wrap/unwrap values atomically, and add a
docblock on remember() explaining that nulls are now supported via the
sentinel/wrapper and how callers should interpret returned values.

In `@resources/views/components/docs/footer.blade.php`:
- Around line 21-23: Replace the inline SVG heart markup with the project's Flux
icon component (e.g., flux:icon.heart or the x-flux component variant) so it
matches the other icons like flux:icon.github, flux:icon.reddit and flux:icon.x;
preserve the existing attributes and classes (class="inline-flex shrink-0
text-zinc-400 [:where(&amp;)]:size-4", data-flux-icon, view/accessibility attrs
such as aria-hidden and data-slot) when instantiating the Flux icon component so
visual styling and a11y remain identical.

In `@resources/views/components/docs/toc.blade.php`:
- Around line 17-25: The external-link icon
(flux:icon.square-arrow-out-up-right) is misleading for the internal route
rendered by flux:navlist.item (href="{{ route('command-index') }}"); remove the
flux:icon.square-arrow-out-up-right element from the Command Index nav item or
replace it with an internal/navigation-appropriate icon (e.g.,
flux:icon.chevron-right or a simple caret) so the visual affordance matches
internal navigation; update the markup inside the flux:navlist.item's div (the
element containing <span>Command Index</span>) accordingly.

In `@tests/Feature/DocsClearCommandTest.php`:
- Around line 8-27: The test leaves a per-run cache directory under
storage/framework/cache/docs-output-tests/ (created in the it(...) test using
DocsOutputCacheService::class and config()->set('docs.cache.path', ...)), so add
an afterEach teardown that reads the configured cache path via
config('docs.cache.path') and, if it exists and File::isDirectory(...) is true,
removes it with File::deleteDirectory(...); place this afterEach alongside the
test so the unique test cache is cleaned up after each run.

In `@tests/Fixtures/docs/docs/link-behavior.md`:
- Line 7: The link target `#local-anchor` in
tests/Fixtures/docs/docs/link-behavior.md points to no heading and creates
ambiguity; either add a matching heading (e.g., insert `## Local Anchor`
somewhere in the document to create the `#local-anchor` target) or explicitly
mark the link as intentionally invalid with a short comment near the link
explaining it tests invalid-anchor behavior so readers know it's deliberate.

In `@tests/Unit/DocumentServiceTest.php`:
- Around line 61-86: The test duplicates the fingerprint computation from
DocumentService::fingerprint(), which makes it brittle; change the test to call
the service's fingerprint logic instead of reimplementing it (e.g., expose
fingerprint() for test usage or add a package-visible/protected accessor on
DocumentService and call that in the test) so the cache key uses
DocumentService::fingerprint() directly when building the cache key for
DocsOutputCacheService::key('document', [...]); update the test to retrieve the
fingerprint from the service and use that value when putting the cached payload.

In `@tests/Unit/MarkdownServiceTest.php`:
- Around line 93-108: The test "it('renders important alerts with block
content')" couples behavior to a Tailwind class; update the assertion to check
for a stable semantic marker instead of 'dark:border-l-amber-400' — for example
assert that the generated HTML from
app(MarkdownService::class)->toHtml($markdown) contains a role or data attribute
like role="alert" or data-alert="important" (or add such an attribute in the
alert rendering code if missing) and keep the existing checks for the list
items; reference the test block name and the MarkdownService::toHtml call to
locate where to change the assertion.


<div class="flex items-center gap-3">
{{-- GitHub Button --}}
<flux:button icon="github" size="sm" href="https://github.com/loadinglucian/deployer-php/" target="_blank">GitHub</flux:button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add rel="noopener" to external link for security.

The GitHub button opens in a new tab but lacks rel="noopener". This is a security best practice to prevent the new page from accessing window.opener, which could enable tab-nabbing attacks.

🛡️ Proposed fix
-        <flux:button icon="github" size="sm" href="https://github.com/loadinglucian/deployer-php/" target="_blank">GitHub</flux:button>
+        <flux:button icon="github" size="sm" href="https://github.com/loadinglucian/deployer-php/" target="_blank" rel="noopener">GitHub</flux:button>
📝 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
<flux:button icon="github" size="sm" href="https://github.com/loadinglucian/deployer-php/" target="_blank">GitHub</flux:button>
<flux:button icon="github" size="sm" href="https://github.com/loadinglucian/deployer-php/" target="_blank" rel="noopener">GitHub</flux:button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/views/components/docs/header.blade.php` at line 19, The external
GitHub link rendered by the flux:button component currently opens with
target="_blank" but lacks rel="noopener"; update the flux:button invocation (the
element with icon="github", size="sm",
href="https://github.com/loadinglucian/deployer-php/") to include rel="noopener"
(or rel="noopener noreferrer") so the new tab cannot access window.opener and to
mitigate tab-nabbing attacks.

@loadinglucian
loadinglucian merged commit 9665b58 into main Feb 20, 2026
7 checks passed
@loadinglucian
loadinglucian deleted the feat/cheat-sheet-livewire-sections branch February 20, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant