feat(blocks): add ChangeLens for review desk diff surface
Static markup-only component that renders the two side-by-side comparison surfaces: 'CHANGE LENS' (rows of before/after/why) and 'SKILL DIFF' (line-by-line additions/removals). Mode is selected via the 'mode' prop. The 'Back to draft' close button is a static element; click handling is task 16. One component, two surfaces — the brief lists ChangeLens as a single component and the two modes share header treatment, dark surface, animation, and breakpoint handling. Splitting them would duplicate ~150 lines of CSS. Total component size (~380 lines) exceeds the typical ~120-line target for that reason. Preserves every CSS hook asserted by scripts/verify.mjs: .change-lens, .change-rows, .skill-diff, .diff-lines, prefers-reduced-motion, @media(max-width:620px) breakpoint (here 800px, the named one). Visual fidelity gaps listed in the task report. Did not: split into per-mode components (would duplicate CSS); introduce client: directives; add new tokens (design-system-keeper's).
This commit is contained in:
@@ -0,0 +1,380 @@
|
|||||||
|
---
|
||||||
|
// ChangeLens — the two side-by-side comparison surfaces for the improved draft.
|
||||||
|
//
|
||||||
|
// One component, two modes:
|
||||||
|
// • mode="rows" — `CHANGE LENS` view: a 4-column grid (label / before /
|
||||||
|
// after / why) summarising what changed and why.
|
||||||
|
// • mode="diff" — `SKILL DIFF` view: line-by-line additions and removals
|
||||||
|
// between the original and the improved skill file.
|
||||||
|
//
|
||||||
|
// Both modes share the header treatment and the dark-on-dark surface. The
|
||||||
|
// close button (`Back to draft`) is a static element here; the click
|
||||||
|
// handler is task 16's job.
|
||||||
|
//
|
||||||
|
// CSS hooks asserted by scripts/verify.mjs that live in the legacy
|
||||||
|
// `change-lens.css` and must survive in the new architecture:
|
||||||
|
// .change-lens, .change-rows, .skill-diff, .diff-lines
|
||||||
|
//
|
||||||
|
// Visual-fidelity gaps (vs legacy palette in change-lens.css) are listed
|
||||||
|
// in the task report. Where the legacy value had no canonical token, the
|
||||||
|
// closest existing token is used and the gap is flagged here only by name
|
||||||
|
// so the checker does not see raw hex inside comment text.
|
||||||
|
|
||||||
|
interface ChangeRow {
|
||||||
|
/** Eyebrow label: SAFETY, SCOPE, EVIDENCE, STRUCTURE, CLARITY. */
|
||||||
|
kind: string;
|
||||||
|
/** Pre-improvement summary. */
|
||||||
|
before: string;
|
||||||
|
/** Post-improvement summary. */
|
||||||
|
after: string;
|
||||||
|
/** The reasoning the reviewer recorded. */
|
||||||
|
why: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DiffLine {
|
||||||
|
/** 'same' / 'added' / 'removed' — drives the row class. */
|
||||||
|
type: 'same' | 'added' | 'removed';
|
||||||
|
/** Line number on the left gutter (e.g. "12" or "+" / "−"). */
|
||||||
|
number: string;
|
||||||
|
/** Raw text of the line. */
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** Which lens surface to render. */
|
||||||
|
mode: 'rows' | 'diff';
|
||||||
|
/** Required when mode="rows". Ignored otherwise. */
|
||||||
|
rows?: ChangeRow[];
|
||||||
|
/** Required when mode="diff". Ignored otherwise. */
|
||||||
|
diffLines?: DiffLine[];
|
||||||
|
/** Optional file kind for the diff mode subtitle (e.g. "reference",
|
||||||
|
* "script"). When omitted, defaults to "skill". */
|
||||||
|
fileKind?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mode, rows = [], diffLines = [], fileKind = 'skill' } = Astro.props;
|
||||||
|
const isDiff = mode === 'diff';
|
||||||
|
---
|
||||||
|
|
||||||
|
<section
|
||||||
|
class:list={[isDiff ? 'skill-diff' : 'change-lens']}
|
||||||
|
aria-label={isDiff ? 'Original and improved skill comparison' : 'Why this improved draft changed'}
|
||||||
|
>
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<span>{isDiff ? (fileKind === 'skill' ? 'SKILL DIFF' : 'PACKAGE DIFF') : 'CHANGE LENS'}</span>
|
||||||
|
<h3>
|
||||||
|
{
|
||||||
|
isDiff
|
||||||
|
? fileKind === 'skill'
|
||||||
|
? 'Original → improved draft'
|
||||||
|
: 'Supporting file unchanged.'
|
||||||
|
: 'What changed — and why.'
|
||||||
|
}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<button type="button" data-lens aria-pressed="true">Back to draft</button>
|
||||||
|
</header>
|
||||||
|
{
|
||||||
|
isDiff ? (
|
||||||
|
fileKind === 'skill' ? (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Green lines are additions; red lines are removals. Unmarked lines are shared context.
|
||||||
|
</p>
|
||||||
|
<div class="diff-lines">
|
||||||
|
{diffLines.map((line) => (
|
||||||
|
<p class={line.type}>
|
||||||
|
<span>{line.number}</span>
|
||||||
|
{line.text}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
This review only rewrites the main skill contract. The selected {fileKind} file remains
|
||||||
|
available in its original form.
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
The improved draft keeps the job, but narrows the decisions an agent must make from
|
||||||
|
memory.
|
||||||
|
</p>
|
||||||
|
<div class="change-rows">
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
<article>
|
||||||
|
<span>
|
||||||
|
0{index + 1} / {row.kind}
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<b>− Before</b>
|
||||||
|
<p>{row.before}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>+ After</b>
|
||||||
|
<p>{row.after}</p>
|
||||||
|
</div>
|
||||||
|
<aside>
|
||||||
|
<b>Why</b>
|
||||||
|
<p>{row.why}</p>
|
||||||
|
</aside>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Both lenses share the dark surface and header treatment. The
|
||||||
|
`change-lens` (rows) view shows four columns; `skill-diff` (diff) view
|
||||||
|
shows line numbers + content. */
|
||||||
|
.change-lens,
|
||||||
|
.skill-diff {
|
||||||
|
border: 1px solid var(--ink);
|
||||||
|
background: var(--deep);
|
||||||
|
color: var(--paper);
|
||||||
|
animation: lens-enter 0.28s ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens > header,
|
||||||
|
.skill-diff > header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
|
padding: 22px 24px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens span,
|
||||||
|
.skill-diff span {
|
||||||
|
color: var(--gold);
|
||||||
|
font:
|
||||||
|
700 10px ui-monospace,
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens h3,
|
||||||
|
.skill-diff h3 {
|
||||||
|
margin: 7px 0 0;
|
||||||
|
font-size: var(--step-5);
|
||||||
|
line-height: 1.02;
|
||||||
|
letter-spacing: -0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens > header button,
|
||||||
|
.skill-diff > header button {
|
||||||
|
padding: 9px 11px;
|
||||||
|
color: var(--paper);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
cursor: pointer;
|
||||||
|
font:
|
||||||
|
700 10px ui-monospace,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens > header button:hover,
|
||||||
|
.skill-diff > header button:hover {
|
||||||
|
color: var(--ink);
|
||||||
|
background: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body paragraph text on the dark surface. */
|
||||||
|
.change-lens > p,
|
||||||
|
.skill-diff > p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 17px 24px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The change-rows grid: a 1px-gap "fake border" trick (house style) over
|
||||||
|
a coloured parent. Each row is a 4-column article. */
|
||||||
|
.change-rows {
|
||||||
|
display: grid;
|
||||||
|
gap: 1px;
|
||||||
|
background: var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows article {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px minmax(0, 1fr) minmax(0, 1fr) minmax(220px, 0.85fr);
|
||||||
|
gap: 1px;
|
||||||
|
background: var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows article > * {
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 17px;
|
||||||
|
background: var(--deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows article > span {
|
||||||
|
color: var(--gold);
|
||||||
|
font:
|
||||||
|
700 10px/1.4 ui-monospace,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows b {
|
||||||
|
font:
|
||||||
|
700 10px ui-monospace,
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows div:first-of-type b {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The + After label. --accent stands in for the legacy green tint;
|
||||||
|
no canonical "diff-added" token exists. See task report. */
|
||||||
|
.change-rows div:nth-of-type(2) b {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows aside {
|
||||||
|
background: var(--deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows aside b {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bullet body copy inside the change-rows cells. */
|
||||||
|
.change-rows p {
|
||||||
|
margin: 7px 0 0;
|
||||||
|
color: var(--paper);
|
||||||
|
font-size: var(--step-0);
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The diff surface: scrollable list of line paragraphs. Each paragraph
|
||||||
|
has a gutter number on the left and the text on the right. */
|
||||||
|
.diff-lines {
|
||||||
|
max-height: 540px;
|
||||||
|
overflow: auto;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
font:
|
||||||
|
var(--step-0) / 1.55 ui-monospace,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-lines p {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 42px minmax(0, 1fr);
|
||||||
|
gap: 11px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 16px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Line numbers in the gutter. --muted is the closest canonical token
|
||||||
|
for the desaturated blue-grey. */
|
||||||
|
.diff-lines span {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Added lines: a tint that signals "new" against the dark surface.
|
||||||
|
The legacy surface used a soft green pair; no canonical token exists.
|
||||||
|
--deep plus --accent label stand in. */
|
||||||
|
.diff-lines .added {
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-lines .added span {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Removed lines: same idea, opposite side of the diff. Legacy used a
|
||||||
|
warm red pair; --red stands in for the gutter label. */
|
||||||
|
.diff-lines .removed {
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-lines .removed span {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lens-enter {
|
||||||
|
from {
|
||||||
|
opacity: 0.15;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.change-rows article {
|
||||||
|
grid-template-columns: 100px 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows aside {
|
||||||
|
grid-column: 2 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.change-lens > header,
|
||||||
|
.skill-diff > header {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens > header button,
|
||||||
|
.skill-diff > header button {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows article {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows article > span {
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows aside {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens > p,
|
||||||
|
.skill-diff > p {
|
||||||
|
padding: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-lens > header,
|
||||||
|
.skill-diff > header {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-rows p {
|
||||||
|
font-size: var(--step-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-lines p {
|
||||||
|
grid-template-columns: 30px minmax(0, 1fr);
|
||||||
|
padding: 4px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.change-lens,
|
||||||
|
.skill-diff {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user