Skip to content

feat: add Copy for LLM sticky header button on blog posts#108

Open
Gowiem wants to merge 1 commit intomasterfrom
feat/copy-for-llm
Open

feat: add Copy for LLM sticky header button on blog posts#108
Gowiem wants to merge 1 commit intomasterfrom
feat/copy-for-llm

Conversation

@Gowiem
Copy link
Copy Markdown
Member

@Gowiem Gowiem commented Apr 12, 2026

what

  • Adds a "Copy for LLM" button on blog posts that copies the full page content as clean markdown to the clipboard, optimized for pasting into AI assistants (Claude, ChatGPT, etc.)
  • The button lives in a new sticky header bar that slides in from the top when the user scrolls past the main page header
  • The sticky bar also displays the truncated blog post title for context
  • Blog-only feature — does not appear on homepage, services, case studies, or other pages

why

  • Makes it easy for readers (and ourselves) to grab a blog post's content in a format optimized for LLM consumption
  • This pattern is becoming standard on developer-focused content sites (Stripe docs, Vercel docs, etc.)
  • Improves the utility of our blog content in the age of AI assistants

how it works

  • Hugo partial (blog-sticky-header.html): Renders the sticky bar HTML and embeds the raw markdown content at build time using Hugo's .RawContent in a hidden <script type="text/plain"> tag
  • JavaScript (copy-for-llm.js): Handles scroll-based visibility (shows bar when header scrolls out of view), clipboard copy via navigator.clipboard.writeText() with execCommand fallback, and cleans Hugo shortcodes (lightboximg → markdown images, signup → removed, button/buttonout → markdown links)
  • SCSS: Sticky bar styled with $pine background matching the blog header, CSS transform slide-down animation, responsive (title hidden on mobile, button goes full-width)
  • Copied content format:
    # Blog Post Title
    
    URL: https://masterpoint.io/blog/slug/
    Author: Author Name
    Published: January 2, 2006
    
    > Description text
    
    [Full markdown content]
    

references

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a sticky header to blog posts displaying the post title and a "Copy for LLM" button.
    • The sticky header automatically appears and disappears based on scroll position.
    • Users can now copy blog content to clipboard with one click, with automatic formatting cleanup applied.

Adds a sticky header bar that slides in from the top when users scroll
past the main header on blog posts. The bar displays the post title and
a "Copy for LLM" button that copies the full blog content as clean
markdown to the clipboard, optimized for pasting into AI assistants.

- New partial: blog-sticky-header.html with embedded raw markdown via
  Hugo's .RawContent in a hidden script tag
- New JS: copy-for-llm.js with scroll detection, clipboard copy,
  shortcode cleaning (lightboximg → markdown images, signup → removed),
  and "Copied!" feedback animation
- SCSS: sticky bar styled with $pine background, CSS transform slide
  transition, responsive (title hidden on mobile)
- Blog-only: does not appear on homepage, services, or case study pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Gowiem Gowiem requested a review from a team as a code owner April 12, 2026 19:46
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 12, 2026

Deploy Preview for masterpoint ready!

Name Link
🔨 Latest commit ba4d3f6
🔍 Latest deploy log https://app.netlify.com/projects/masterpoint/deploys/69dbf6a9a056470008f4cc3f
😎 Deploy Preview https://deploy-preview-108--masterpoint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 48
Accessibility: 86
Best Practices: 92
SEO: 79
PWA: 70
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

Walkthrough

Adds a sticky header feature to blog posts that displays the post title and a "Copy for LLM" button. When the page header scrolls out of view, the sticky header becomes visible at the top. The copy function cleans and transforms the post content before sending it to the clipboard.

Changes

Cohort / File(s) Summary
Blog Sticky Header Styling
assets/css/custom.scss
Added fixed-position sticky header styling with transform-based offscreen behavior, title styling, copy button styling with hover states, responsive adjustments for tablet and mobile breakpoints, and visual feedback for successful copy action.
Copy for LLM Functionality
assets/js/copy-for-llm.js
Implemented scroll listener with throttling to toggle sticky header visibility, click handler with content cleaning pipeline (URL normalization, shortcode conversion, whitespace normalization), and clipboard copy with fallback implementation and UI feedback.
Blog Template Integration
layouts/_default/single.html, layouts/partials/blog-sticky-header.html, layouts/partials/scripts.html
Added conditional rendering of sticky header partial for blog posts, created blog-sticky-header partial that embeds post metadata and raw content, and integrated minified copy script loading for blog section.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant DOM
    participant Clipboard

    User->>Browser: Scroll page
    Browser->>DOM: Fire scroll event (throttled 50ms)
    DOM->>DOM: Get header bounding rect
    alt Header scrolled out of view
        DOM->>DOM: Add 'visible' class to sticky header
        DOM->>User: Display sticky header
    else Header still visible
        DOM->>DOM: Remove 'visible' class
        DOM->>User: Hide sticky header
    end

    User->>DOM: Click "Copy for LLM" button
    DOM->>DOM: Extract rawMarkdownContent
    DOM->>DOM: Clean content<br/>(normalize URLs,<br/>convert shortcodes,<br/>normalize whitespace)
    DOM->>Clipboard: Write cleaned text
    Clipboard->>User: Show "Copied!" feedback
    Note over User,DOM: Feedback removed after 2 seconds
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • oycyc
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature addition: a 'Copy for LLM' button in a sticky header for blog posts, which directly matches the core objective of the pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/copy-for-llm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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: 2

🧹 Nitpick comments (1)
assets/css/custom.scss (1)

3569-3571: Improve copied-state contrast for button readability.

#2ecc71 with inherited white text is low-contrast for the small button label. Set an explicit darker text color (or darker green background) for accessible contrast.

🎨 Suggested tweak
     &.copied {
       background: `#2ecc71`;
+      color: `#0b2b2c`;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/css/custom.scss` around lines 3569 - 3571, The .copied state (&.copied
in assets/css/custom.scss) uses background `#2ecc71` which yields low contrast
with white text; update the rule to ensure accessible contrast by either setting
an explicit darker text color (e.g., color: a dark green/near-black) or using a
darker background (e.g., darken(`#2ecc71`, 15-25%)) so the resulting contrast
ratio meets WCAG (>=4.5:1) for small button labels; apply the change to the
&.copied selector and prefer using your existing color variables or a
darken()/mix() helper so it stays consistent with the theme.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/js/copy-for-llm.js`:
- Around line 56-58: The copyToClipboard(content).then(...) chain currently only
handles success and can produce unhandled promise rejections; add a .catch(...)
on the promise returned by copyToClipboard to handle failures, log the error
(e.g., console.error or existing logger) and call a user-facing failure handler
(create or reuse a function like showCopyFailedFeedback or
showCopyErrorFeedback) so users get visible feedback instead of silent failures;
update the call site where copyToClipboard and showCopiedFeedback are used to
include this .catch and ensure the error handler is implemented.

In `@layouts/partials/blog-sticky-header.html`:
- Around line 9-17: The template is using the safeHTML transformer inside a
<script> payload which can allow content containing </script> to break out and
cause script-breakout injection; remove the use of safeHTML for the fields
rendered inside the script block (e.g. .RawContent, .Title, .Permalink,
.Params.description, .Params.author) and instead render them with the default
auto-escaping or a JSON-encoding helper (e.g. jsonify) or an HTML-escaping
helper appropriate for Hugo templates so the content is safely escaped and
cannot prematurely close the <script> tag.

---

Nitpick comments:
In `@assets/css/custom.scss`:
- Around line 3569-3571: The .copied state (&.copied in assets/css/custom.scss)
uses background `#2ecc71` which yields low contrast with white text; update the
rule to ensure accessible contrast by either setting an explicit darker text
color (e.g., color: a dark green/near-black) or using a darker background (e.g.,
darken(`#2ecc71`, 15-25%)) so the resulting contrast ratio meets WCAG (>=4.5:1)
for small button labels; apply the change to the &.copied selector and prefer
using your existing color variables or a darken()/mix() helper so it stays
consistent with the theme.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 77971a7b-0170-4d99-9d00-7a2aca9555fd

📥 Commits

Reviewing files that changed from the base of the PR and between 634e2fa and ba4d3f6.

📒 Files selected for processing (5)
  • assets/css/custom.scss
  • assets/js/copy-for-llm.js
  • layouts/_default/single.html
  • layouts/partials/blog-sticky-header.html
  • layouts/partials/scripts.html

Comment thread assets/js/copy-for-llm.js
Comment on lines +56 to +58
copyToClipboard(content).then(function () {
showCopiedFeedback();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle clipboard copy failures to avoid silent/unhandled errors.

Lines 56-58 only handle success. Add a .catch(...) to prevent unhandled promise rejection and give the user failure feedback.

🛠️ Suggested fix
-      copyToClipboard(content).then(function () {
-        showCopiedFeedback();
-      });
+      copyToClipboard(content)
+        .then(function () {
+          showCopiedFeedback();
+        })
+        .catch(function () {
+          var originalHTML = copyBtn.innerHTML;
+          copyBtn.innerHTML = '<i class="fa fa-times"></i> Copy failed';
+          setTimeout(function () {
+            copyBtn.innerHTML = originalHTML;
+          }, 2000);
+        });
📝 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
copyToClipboard(content).then(function () {
showCopiedFeedback();
});
copyToClipboard(content)
.then(function () {
showCopiedFeedback();
})
.catch(function () {
var originalHTML = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fa fa-times"></i> Copy failed';
setTimeout(function () {
copyBtn.innerHTML = originalHTML;
}, 2000);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/copy-for-llm.js` around lines 56 - 58, The
copyToClipboard(content).then(...) chain currently only handles success and can
produce unhandled promise rejections; add a .catch(...) on the promise returned
by copyToClipboard to handle failures, log the error (e.g., console.error or
existing logger) and call a user-facing failure handler (create or reuse a
function like showCopyFailedFeedback or showCopyErrorFeedback) so users get
visible feedback instead of silent failures; update the call site where
copyToClipboard and showCopiedFeedback are used to include this .catch and
ensure the error handler is implemented.

Comment on lines +9 to +17
# {{ .Title | safeHTML }}

URL: {{ .Permalink }}
{{ with .Params.author }}Author: {{ . }}
{{ end }}Published: {{ .Date.Format "January 2, 2006" }}
{{ with .Params.description }}
> {{ . | safeHTML }}
{{ end }}
{{ .RawContent | safeHTML }}</script>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove safeHTML inside the embedded script payload to avoid script-breakout injection.

At Line 17 (and similarly Lines 9 and 15), rendering raw content with safeHTML inside a <script> block can prematurely close the script if content contains </script>, breaking the page and creating an injection vector.

🔧 Suggested fix
 <script id="rawMarkdownContent" type="text/plain">
-# {{ .Title | safeHTML }}
+# {{ .Title }}
 
 URL: {{ .Permalink }}
 {{ with .Params.author }}Author: {{ . }}
 {{ end }}Published: {{ .Date.Format "January 2, 2006" }}
 {{ with .Params.description }}
-> {{ . | safeHTML }}
+> {{ . }}
 {{ end }}
-{{ .RawContent | safeHTML }}</script>
+{{ .RawContent }}</script>
📝 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
# {{ .Title | safeHTML }}
URL: {{ .Permalink }}
{{ with .Params.author }}Author: {{ . }}
{{ end }}Published: {{ .Date.Format "January 2, 2006" }}
{{ with .Params.description }}
> {{ . | safeHTML }}
{{ end }}
{{ .RawContent | safeHTML }}</script>
# {{ .Title }}
URL: {{ .Permalink }}
{{ with .Params.author }}Author: {{ . }}
{{ end }}Published: {{ .Date.Format "January 2, 2006" }}
{{ with .Params.description }}
> {{ . }}
{{ end }}
{{ .RawContent }}</script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@layouts/partials/blog-sticky-header.html` around lines 9 - 17, The template
is using the safeHTML transformer inside a <script> payload which can allow
content containing </script> to break out and cause script-breakout injection;
remove the use of safeHTML for the fields rendered inside the script block (e.g.
.RawContent, .Title, .Permalink, .Params.description, .Params.author) and
instead render them with the default auto-escaping or a JSON-encoding helper
(e.g. jsonify) or an HTML-escaping helper appropriate for Hugo templates so the
content is safely escaped and cannot prematurely close the <script> tag.

Copy link
Copy Markdown
Contributor

@oycyc oycyc left a comment

Choose a reason for hiding this comment

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

not much to look at through the code, but the preview looks good and the actual copy and pasting works

Image

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.

2 participants