Plannotator pierre-guard
Guard against breaking the @pierre/diffs integration in Plannotator's code review UI. Use this skill whenever modifying DiffViewer.tsx, upgrading the @pierre/diffs package, changing unsafeCSS injection, adding new props to FileDiff, or touching shadow DOM selectors or CSS variables that cross into Pierre's shadow boundary. Also trigger when someone asks "will this break the diff viewer", "is this safe to change", or when reviewing PRs that touch the review-editor package.
git clone https://github.com/backnotprop/plannotator
T=$(mktemp -d) && git clone --depth=1 https://github.com/backnotprop/plannotator "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/pierre-guard" ~/.claude/skills/backnotprop-plannotator-pierre-guard && rm -rf "$T"
.agents/skills/pierre-guard/SKILL.mdPierre Integration Guard
Plannotator's code review UI wraps
@pierre/diffs — an open-source diff renderer that uses Shadow DOM. The integration is concentrated in a single file but relies on undocumented internals (shadow DOM selectors, CSS variable names, grid layout assumptions). This skill helps verify changes don't break that contract.
Source of Truth
- Upstream repo: https://github.com/pierrecomputer/pierre/tree/main/packages/diffs
- Local types:
(node_modules/@pierre/diffs/dist/
files).d.ts - Integration point:
packages/review-editor/components/DiffViewer.tsx - Current version: check
for the pinned versionpackages/review-editor/package.json
Always verify against the upstream repo or local
.d.ts files — don't rely on memory of the API shape.
What We Import
import { FileDiff } from '@pierre/diffs/react'; import { getSingularPatch, processFile } from '@pierre/diffs';
These are the only three imports.
DiffViewer.tsx is the only file that touches Pierre.
API Surface to Guard
1. Component Props (FileDiff
)
FileDiffRead the current prop types from
node_modules/@pierre/diffs/dist/react/index.d.ts or the upstream source. The props we use:
| Prop | Type | Notes |
|---|---|---|
| | From or |
| | See options table below |
| | |
| | |
| | Custom inline annotation renderer |
| | The button on hover (deprecated upstream — watch for removal) |
2. Options Object
| Option | Value We Pass | Risk |
|---|---|---|
| | Low — standard enum |
| CSS string | High — targets internal selectors |
| | Low — standard enum |
| | Low |
| | Low |
| | Low |
| | Medium — deprecated prop |
| callback | Medium — signature could change |
3. Shadow DOM Selectors (via unsafeCSS
)
unsafeCSSThese are the selectors we inject CSS rules against. They target
data-* attributes inside Pierre's shadow DOM. If Pierre renames or removes any of these, our styling breaks silently.
Currently used:
— shadow root:host
— root diff container[data-diff]
— file wrapper[data-file]
— header bar[data-diffs-header]
— error display[data-error-wrapper]
— virtual scroll buffer[data-virtualizer-buffer]
— file metadata row[data-file-info]
— line number gutter[data-column-number]
— title (we hide it)[data-diffs-header] [data-title]
— split layout mode[data-diff-type='split']
/[data-overflow='scroll']
— overflow mode[data-overflow='wrap']
4. CSS Variables We Override
We override these
--diffs-* variables to theme Pierre:
,--diffs-bg
— base colors--diffs-fg
,--diffs-dark-bg
— theme-specific backgrounds--diffs-light-bg
,--diffs-dark
— theme-specific foregrounds--diffs-light
5. CSS Variables We Inject (Custom)
We set these on a wrapper div outside the shadow DOM, relying on CSS custom property inheritance:
,--split-left
— control the split pane grid ratio--split-right
The
unsafeCSS grid override references these: grid-template-columns: var(--split-left, 1fr) var(--split-right, 1fr). The 1fr fallback ensures the layout is safe if the variables aren't set.
6. Grid Layout Assumption
Pierre's split view uses CSS Grid with
grid-template-columns: 1fr 1fr. We override this for the resizable split pane. If Pierre changes its layout engine (e.g., to flexbox or a different grid structure), the override will stop working.
How to verify: In the upstream source, search for
grid-template-columns in the diff component styles.
Verification Checklist
When reviewing changes that touch the Pierre integration, check:
Props & Types
- Read the current
files to confirm prop names and types haven't changed.d.ts - Check if
is still supported (it's deprecated — may be removed)renderHoverUtility - Verify
still usesDiffLineAnnotation
(notside: 'deletions' | 'additions'
)'old' | 'new' - Confirm
shape:SelectedLineRange{ start, end, side? }
Shadow DOM Selectors
- Grep the upstream source for each
attribute we target indata-*unsafeCSS - If upgrading the package version, diff the old and new CSS/HTML output for renamed attributes
- Test both
andsplit
views — selectors are layout-dependentunified
CSS Variables
- Grep upstream for
,--diffs-bg
, and other variables we override--diffs-fg - Verify the variable names haven't been renamed or removed
- Check that
is still needed (Pierre may change specificity)!important
Theme Compliance
- New UI elements must use theme tokens (
,bg-border
, etc.), not hardcoded colors likebg-primarybg-blue-500 - The existing
component inResizeHandle
sets the visual convention — match itpackages/ui/components/ResizeHandle.tsx
Build & Runtime
- Run
and verify the diff renders in both split and unified modesbun run dev:review - Check the browser console for Pierre warnings (e.g.,
)parseLineType: Invalid firstChar - Test with add-only and delete-only files (Pierre doesn't render split grid for these)
- If changing UI code, remember build order:
bun run --cwd apps/review build && bun run build:hook
When Upgrading @pierre/diffs
- Check the upstream changelog / commit history at https://github.com/pierrecomputer/pierre
- Diff the
files between old and new versions:.d.ts# Before upgrading, snapshot current types cp -r node_modules/@pierre/diffs/dist /tmp/pierre-old # After upgrading diff -r /tmp/pierre-old node_modules/@pierre/diffs/dist - Search for renamed/removed data attributes in the new version
- Run through the full verification checklist above
- Test the resizable split pane — it depends on grid layout internals