Skip to content

fix(ui): clipboard copy for http://0.0.0.0:3000 and unify implementations#2487

Merged
CommanderStorm merged 22 commits intomainfrom
copilot/fix-clipboard-copy-functionality
Jan 6, 2026
Merged

fix(ui): clipboard copy for http://0.0.0.0:3000 and unify implementations#2487
CommanderStorm merged 22 commits intomainfrom
copilot/fix-clipboard-copy-functionality

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 5, 2026

  • Create a unified copyToClipboard utility function in martin-ui/src/lib/utils.ts
    • Implement primary approach using navigator.clipboard.writeText()
    • Add fallback using document.execCommand('copy') for non-secure contexts
  • Create simplified useCopyToClipboard hook
    • Exposes copied boolean and copiedValue for per-item tracking
    • Only successMessage is configurable
    • Standardized 2000ms reset delay
  • Fixed sprite-download.tsx to show copied state only for the copied item
  • All 167 tests pass, TypeScript and linting pass
Original prompt

This section details on the original issue you should resolve

<issue_title>Copy to clipboard does not work for http://0.0.0.0:3000</issue_title>
<issue_description>By default, Martin starts at http://0.0.0.0:3000 -- which is usually the same as http://localhost:3000 with one caveat: navigator.clipboard is not available for "non-https non-localhost" cases - i.e. Copy to clipboard is not working. A simple workaround would be to use something like this code in case of an error, or one of numerous NPM libs. I tested it locally, seem to work.

P.S. Is there a reason why there are so many near-duplicate clipboard copy implementations? Seems like a good target for code reuse.

const el = document.createElement('textarea');
el.value = text;
el.style.position = 'fixed';
el.style.opacity = '0';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
```</issue_description>

<agent_instructions>handle localhost in navigator.clipboard and unify the clipboard implementations</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits January 5, 2026 16:23
…host contexts

Fixes #1673: Copy to clipboard does not work for http://0.0.0.0:3000

- Created unified copyToClipboard utility in lib/utils.ts with fallback
- Uses navigator.clipboard.writeText when available (secure contexts)
- Falls back to document.execCommand('copy') for non-HTTPS/non-localhost
- Added comprehensive tests for the new utility
- Refactored all 5 clipboard implementations to use unified utility:
  - tooltip-copy-text.tsx
  - copy-link-button.tsx
  - SpriteCanvas.tsx
  - sprite-download.tsx
  - style-integration-guide.tsx

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
- Position textarea off-screen with left/top instead of opacity
- Add aria-hidden attribute for better accessibility
- Use setSelectionRange for more reliable selection
- Add focus() before selection for better compatibility

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix copy to clipboard functionality for localhost Fix clipboard copy for http://0.0.0.0:3000 and unify implementations Jan 5, 2026
Copilot AI requested a review from CommanderStorm January 5, 2026 16:26
@CommanderStorm
Copy link
Copy Markdown
Member

@copilot

  • please test properly using vitest mocking best practices instead of Object.defineProperty
  • please make sure to reduce the number of competing clipboard implementations while we are at it

- Replace Object.defineProperty with vi.stubGlobal for navigator mocking
- Use vi.unstubAllGlobals and vi.restoreAllMocks for proper cleanup
- Add execCommand directly to document object (jsdom doesn't have it)
- Avoid stubbing entire document object to prevent unexpected behavior

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot

  • please test properly using vitest mocking best practices instead of Object.defineProperty
  • please make sure to reduce the number of competing clipboard implementations while we are at it

Addressed in c552e5c:

  • Replaced Object.defineProperty with vi.stubGlobal for navigator mocking
  • Using vi.unstubAllGlobals() and vi.restoreAllMocks() for proper cleanup
  • Added execCommand directly to document object rather than stubbing entire document

Clipboard implementations are now unified to a single copyToClipboard utility in lib/utils.ts. All 5 previous implementations now use this shared function.

@CommanderStorm
Copy link
Copy Markdown
Member

@copilot
Do some deep thinking how all usages of the clipboard api work.
Come up with a system that is less duplicate code.

1 similar comment
@CommanderStorm
Copy link
Copy Markdown
Member

@copilot
Do some deep thinking how all usages of the clipboard api work.
Come up with a system that is less duplicate code.

- Create useCopyToClipboard hook that encapsulates:
  - Calling copyToClipboard utility
  - Managing copied/copiedText state with auto-reset
  - Showing success/error toasts with customizable messages
  - Proper timeout cleanup to prevent memory leaks
- Refactor all 5 components to use the new hook:
  - tooltip-copy-text.tsx
  - copy-link-button.tsx
  - SpriteCanvas.tsx
  - sprite-download.tsx
  - style-integration-guide.tsx
- Add comprehensive tests for the new hook
- Fix TypeScript error in utils.test.ts

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot
Do some deep thinking how all usages of the clipboard api work.
Come up with a system that is less duplicate code.

Addressed in 5d8b3d8. Created a useCopyToClipboard hook that encapsulates all clipboard logic:

const { copy, copied, copiedText } = useCopyToClipboard({
  successMessage: 'URL copied!',
  errorMessage: 'Copy failed',
  resetDelay: 2000,
});

The hook handles:

  • Calling the copyToClipboard utility (with fallback for non-HTTPS)
  • Managing copied and copiedText state with auto-reset
  • Showing success/error toasts (configurable)
  • Proper timeout cleanup to prevent memory leaks

All 5 components now use this single hook instead of duplicating the try/catch/toast pattern.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot
Do some deep thinking how all usages of the clipboard api work.
Come up with a system that is less duplicate code.

Addressed in 5d8b3d8. Created a two-layer clipboard abstraction:

  1. copyToClipboard(text) utility in lib/utils.ts - handles the actual clipboard API with fallback for non-HTTPS contexts
  2. useCopyToClipboard(options) hook in hooks/use-copy-to-clipboard.ts - handles state management, toast notifications, and auto-reset

All 5 components now use the hook instead of duplicating clipboard handling logic:

  • Tracks copied boolean and copiedValue for multi-item UIs
  • Configurable success/error messages and reset timeouts
  • 7 tests for the hook, 4 tests for the utility function

- Add useEffect cleanup to clear timeout on unmount
- Remove unused id parameter from CodeBlock component
- Remove unused useId import
- Fix object property sorting to satisfy biome linter

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copy link
Copy Markdown
Member

@CommanderStorm CommanderStorm left a comment

Choose a reason for hiding this comment

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

@copilot

  • handleCopy seems uneessesary and can be inlined
  • martin/martin-ui/src/hooks/use-copy-to-clipboard.ts
    • needs a pass to clean up the code. It is currently written very amateur-ish with way to many comments. reduce the size of the comments to a best practice level
    • can reduce its options, for example always showing the error/success toast, making resetDelay standardised and hard coding the errroMessage. Only successMessage seems like a resonable option for this.

- Remove verbose JSDoc comments, keep single line description
- Hardcode ERROR_MESSAGE and RESET_DELAY constants (no longer configurable)
- Remove showSuccessToast, showErrorToast, errorMessage, resetDelay options
- Keep only successMessage as configurable option
- Remove unused reset() method from hook
- Inline handleCopy in copy-link-button.tsx
- Remove handleCopyUrl wrapper in sprite-download.tsx
- Update tests to match simplified API

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 5, 2026

@copilot

  • handleCopy seems uneessesary and can be inlined
    ...

Addressed in 250384e:

  • Inlined handleCopy in copy-link-button.tsx and removed handleCopyUrl wrapper in sprite-download.tsx
  • Simplified hook to ~65 lines with minimal comments
  • Removed options: showSuccessToast, showErrorToast, errorMessage, resetDelay
  • Hardcoded RESET_DELAY = 2000 and ERROR_MESSAGE as constants
  • Only successMessage remains configurable

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copilot AI requested a review from CommanderStorm January 5, 2026 17:33
@CommanderStorm
Copy link
Copy Markdown
Member

@copilot the sprite download page shares one copied which means that if one item gets copy(), others also get the copied=true -> the green icon.
This is a bug and should not happen.

Make sure this is not true on other pages as well.
Be aware that I removed a few pages without animation potential.
When using a Tooltip, the tooltip disapeares when clicked outside, even if this is the triggers copy()

Add copiedValue to useCopyToClipboard hook to track which specific
value was copied. This fixes the bug where copying one URL would
show the green "Copied" icon on all items in the sprite-download page.

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 6, 2026

@copilot the sprite download page shares one copied which means that if one item gets copy(), others also get the copied=true -> the green icon.
This is a bug and should not happen.

...

Fixed in 9698664.

Added copiedValue to the useCopyToClipboard hook to track which specific value was copied. Now sprite-download.tsx compares copiedValue === format.url to show the green icon only for the item that was actually copied.

The other components are fine:

  • style-integration-guide.tsx - Each CodeBlock has its own hook instance, so no shared state issue
  • tooltip-copy-text.tsx and SpriteCanvas.tsx - Tooltip closes on click, so no icon switch needed (per previous review feedback)

@nyurik
Copy link
Copy Markdown
Member

nyurik commented Jan 6, 2026

do we actually need an indicator that the value was copied, if we also have a toast popping up?

@CommanderStorm
Copy link
Copy Markdown
Member

Well.. it looks nice 😉

@nyurik
Copy link
Copy Markdown
Member

nyurik commented Jan 6, 2026

sounds like a good reason :)

Copy link
Copy Markdown
Member

@CommanderStorm CommanderStorm left a comment

Choose a reason for hiding this comment

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

LGTM now 🎉

@CommanderStorm CommanderStorm marked this pull request as ready for review January 6, 2026 21:28
Copilot AI review requested due to automatic review settings January 6, 2026 21:28
@CommanderStorm CommanderStorm requested a review from nyurik January 6, 2026 21:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes clipboard copy functionality for non-secure contexts (http://0.0.0.0:3000) and unifies multiple near-duplicate clipboard implementations across the UI codebase by introducing a centralized utility and hook.

Key changes:

  • Added copyToClipboard utility with modern Clipboard API and legacy execCommand fallback
  • Created unified useCopyToClipboard hook with toast notifications and auto-reset state
  • Refactored 6 components to use the new unified implementation

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
martin/martin-ui/src/lib/utils.ts Adds centralized copyToClipboard function with fallback for non-secure contexts
martin/martin-ui/src/hooks/use-copy-to-clipboard.ts New hook providing consistent clipboard API with toast notifications and state management
martin/martin-ui/src/components/ui/tooltip-copy-text.tsx Refactored to use unified hook, removing duplicate clipboard logic
martin/martin-ui/src/components/ui/copy-link-button.tsx Migrated to new hook with visual feedback (icon switching)
martin/martin-ui/src/components/sprite/SpriteCanvas.tsx Simplified clipboard handling using new hook
martin/martin-ui/src/components/error/error-state.tsx Updated styling to use gap utility for consistency
martin/martin-ui/src/components/dialogs/style-integration-guide.tsx Refactored CodeBlock to use hook, removed per-block ID tracking
martin/martin-ui/src/components/dialogs/sprite-download.tsx Migrated to hook with per-item copy state using copiedValue
martin/martin-ui/__tests__/lib/utils.test.ts Added comprehensive tests for copyToClipboard including fallback scenarios
martin/martin-ui/__tests__/hooks/use-copy-to-clipboard.test.tsx New test suite for the hook covering success, custom messages, and error cases
martin/martin-ui/package-lock.json Version bump to 1.2.0 and automatic dependency resolution updates
Files not reviewed (1)
  • martin/martin-ui/package-lock.json: Language not supported

variant: 'destructive',
});
});
});
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

Missing test coverage for the copiedValue state tracking. The hook exposes copiedValue which is critical for per-item copy state in sprite-download.tsx (checking if copiedValue === format.url), but there are no tests verifying this functionality. Tests should verify that copiedValue is set correctly and reset after the timeout.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +39
textarea.focus();
textarea.setSelectionRange(0, text.length);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The textarea.focus() call in the fallback clipboard implementation could cause visual focus shifts for keyboard users. Since the textarea is aria-hidden and positioned off-screen, calling focus() might still trigger unexpected focus events or screen reader announcements. Consider using textarea.select() without an explicit focus() call, as select() implicitly focuses the element when needed for the copy operation.

Suggested change
textarea.focus();
textarea.setSelectionRange(0, text.length);
textarea.select();

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +44
Copied
<CopyCheck className="w-3 h-3 dark:text-green-600" />
</>
) : (
<>
Copy
<Copy className="w-3 h-3" />
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The icon and text order is inconsistent. In the "copied" state, the text comes before the icon ("Copied" then CopyCheck), but in the default state, the text comes before the icon ("Copy" then Copy icon). This is different from other components like sprite-download.tsx where the icon consistently appears before the text. Consider using a consistent order for better visual predictability.

Suggested change
Copied
<CopyCheck className="w-3 h-3 dark:text-green-600" />
</>
) : (
<>
Copy
<Copy className="w-3 h-3" />
<CopyCheck className="w-3 h-3 dark:text-green-600" />
<span className="ml-1">Copied</span>
</>
) : (
<>
<Copy className="w-3 h-3" />
<span className="ml-1">Copy</span>

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not inconsistent, this is on purpose. looks very janky otherwise

CommanderStorm and others added 3 commits January 6, 2026 22:34
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@CommanderStorm CommanderStorm merged commit 012053b into main Jan 6, 2026
37 checks passed
@CommanderStorm CommanderStorm deleted the copilot/fix-clipboard-copy-functionality branch January 6, 2026 22:53
@CommanderStorm CommanderStorm mentioned this pull request Jan 5, 2026
CommanderStorm added a commit that referenced this pull request Jan 28, 2026
## 🤖 New release

* `martin-tile-utils`: 0.6.8 -> 0.6.9 (✓ API compatible changes)
* `mbtiles`: 0.15.0 -> 0.15.1 (✓ API compatible changes)
* `martin-core`: 0.2.5 -> 0.2.6 (✓ API compatible changes)
* `martin`: 1.2.0 -> 1.3.0

<details><summary><i><b>Changelog</b></i></summary><p>


## `mbtiles`

<blockquote>

##
[0.15.1](mbtiles-v0.15.0...mbtiles-v0.15.1)
- 2026-01-27

### Added

- add MLT decoding support
([#2512](#2512))
- migrate our log library to tracing
([#2494](#2494))

### Other

- unignore `diff_and_patch_bsdiff` test with unique SQLite database
names ([#2480](#2480))
- *(mbtiles)* remove the prefix-ism around how files are named for
binary diff copy and simpify their naming
([#2478](#2478))
- *(mbtiles)* add assertion messages what we are checking to the copy
tests ([#2477](#2477))
</blockquote>

## `martin-core`

<blockquote>

##
[0.2.6](martin-core-v0.2.5...martin-core-v0.2.6)
- 2026-01-27

### Added

- migrate our log library to tracing
([#2494](#2494))
- *(martin-core)* Allow glyph ranges more than 0xFFFF
([#2438](#2438))

### Fixed

- *(perf)* Remove FontSources.masks as they were consuming large amounts
of memory even when no font sources were set
([#2519](#2519))
- improve error message if no SVG sprite files are present
([#2516](#2516))

### Other

- move our imports to tracing
([#2500](#2500))
- *(deps)* shear our dependencys
([#2497](#2497))
</blockquote>

## `martin`

<blockquote>

##
[1.3.0](martin-v1.2.0...martin-v1.3.0)
- 2026-01-27

### Added

- *(srv)* Add `route_prefix` configuration for native subpath support
without the need of a reverse proxy override
([#2523](#2523))
- add MLT decoding support
([#2512](#2512))
- migrate our log library to tracing
([#2494](#2494))
- improve martin-cp progress output time estimate
([#2491](#2491))
- *(pg)* include ID column info for tables
([#2485](#2485))
- *(pg)* support PostgreSQL materialized views
([#2279](#2279))
- *(martin-core)* Allow glyph ranges more than 0xFFFF
([#2438](#2438))

### Fixed

- *(ui)* clipboard copy for http://0.0.0.0:3000 and unify
implementations ([#2487](#2487))
- the `Copy` icon displaying nicely, next to the text and with enough
padding ot all items
([#2483](#2483))
- update copy text to include icon for better visibility
([#2482](#2482))
- *(perf)* Remove FontSources.masks as they were consuming large amounts
of memory even when no font sources were set
([#2519](#2519))
- improve error message if no SVG sprite files are present
([#2516](#2516))

### Other

- move our request logging to tracing
([#2508](#2508))
- move our imports to tracing
([#2500](#2500))
- *(deps)* shear our dependencys
([#2497](#2497))
- *(ui)* adjust margin for copy icon in URL component
([#2489](#2489))
- unignore `diff_and_patch_bsdiff` test with unique SQLite database
names ([#2480](#2480))
- *(mbtiles)* remove the prefix-ism around how files are named for
binary diff copy and simpify their naming
([#2478](#2478))
- *(mbtiles)* add assertion messages what we are checking to the copy
tests ([#2477](#2477))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.

Copy to clipboard does not work for http://0.0.0.0:3000

4 participants