Skip to content

feat(viewer): show color swatches next to hex color codes in plans#562

Merged
backnotprop merged 1 commit intobacknotprop:mainfrom
Pran-Ker:feat/hex-color-swatches
Apr 14, 2026
Merged

feat(viewer): show color swatches next to hex color codes in plans#562
backnotprop merged 1 commit intobacknotprop:mainfrom
Pran-Ker:feat/hex-color-swatches

Conversation

@Pran-Ker
Copy link
Copy Markdown
Contributor

Summary

When a plan contains a hex color value (#rgb, #rrggbb, #rgba, #rrggbbaa), the InlineMarkdown renderer now displays a small filled swatch inline — immediately to the left of the monospace hex code — so the actual color is visible at a glance without leaving the document.

This is especially useful for frontend development plans where colours are frequently referenced (design tokens, Tailwind overrides, CSS variables, component palette decisions). Without this, reviewers have to mentally decode hex strings or open a colour picker just to follow the author's intent.

Before: use #ff6600 for the CTA button
After: use 🟧 #ff6600 for the CTA button (small inline swatch, not an emoji)

What changed

packages/ui/components/Viewer.tsx — two edits, ~20 lines total:

  1. New pattern block in the InlineMarkdown loop (after inline-code, before wikilinks):

    • Matches #rgb, #rgba, #rrggbb, #rrggbbaa with a negative lookahead that prevents false-positives on URL anchors (#section), CSS id selectors (#sidebar), and any identifier that continues with word characters.
    • Renders a 14×14 rounded swatch (<span style={{ backgroundColor: hex }}>) followed by the hex code in a <code> tag.
  2. nextSpecial regex now includes # so the scanner stops at a # mid-sentence rather than swallowing it as plain text.

Security notes

  • No CSS injection risk. style={{ backgroundColor: hex }} uses React's JS-object style prop, not raw cssText. React sets individual CSS properties, so it is not possible to break out into arbitrary CSS via this value.
  • Value is regex-constrained. The only string that ever reaches backgroundColor is # followed by exactly 3, 4, 6, or 8 hex digits ([0-9a-fA-F]). Payloads like expression(), url(), or ;color:red suffixes are structurally impossible to inject.
  • No innerHTML or dangerouslySetInnerHTML. All rendering is via React JSX — standard XSS protections apply.
  • No ReDoS risk. The regex uses fixed-length alternatives checked from longest to shortest with no nested quantifiers.

Tests

packages/ui/components/hexColorSwatch.test.ts — 19 tests, all passing (bun test):

Group What is tested
Valid colors 3-, 4-, 6-, 8-digit hex; mixed case; word-boundary termination
No false positives 5- and 7-digit sequences, non-hex chars, bare #, URL anchors, CSS id names, 9+ digit strings
Surrounding context Color mid-sentence, followed by punctuation, two colors in sequence
Security Injection payloads never reach backgroundColor; captured value always passes strict /^#[0-9a-fA-F]{3,8}$/ check
bun test packages/ui/components/hexColorSwatch.test.ts
 19 pass
  0 fail

@Pran-Ker Pran-Ker force-pushed the feat/hex-color-swatches branch from f3160c4 to b60e8d1 Compare April 14, 2026 14:57
@backnotprop
Copy link
Copy Markdown
Owner

Nice feature

@Pran-Ker Pran-Ker force-pushed the feat/hex-color-swatches branch from b60e8d1 to 619de1c Compare April 14, 2026 16:47
@Pran-Ker
Copy link
Copy Markdown
Contributor Author

Pran-Ker commented Apr 14, 2026

Add the change @backnotprop applied the more accurate regex.

The 3- and 4-digit alternatives now require at least one a–f letter via a lookahead, so #123 and #1234 pass through as plain text. 6- and 8-digit forms are unchanged.

When a plan contains hex color values (#rgb, #rrggbb, #rgba, #rrggbbaa),
the InlineMarkdown renderer now shows a small filled swatch inline to the
left of the code so the actual color is immediately visible.
@Pran-Ker Pran-Ker force-pushed the feat/hex-color-swatches branch from 619de1c to 237c8c6 Compare April 14, 2026 16:50
@backnotprop backnotprop merged commit 7245546 into backnotprop:main Apr 14, 2026
7 checks passed
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