Files
FabledScribe/frontend/src/components/DiffView.vue
T
bvandeusen 30dfbce426 fix(typography): strip chrome italic; reserve italic for emphasis
Per the design rule "italic is for emphasis, not for design", removed
every chrome `font-style: italic` declaration across the frontend.
42 declarations gone across 24 files: empty-state placeholder copy,
loading messages, "no results" hints, ghost text, voice/role labels,
field placeholder text, brand wordmark, et al.

Markdown content emphasis is unaffected — `<em>` and `<i>` tags from
`*emphasis*` markup still render italic via browser default styling
(prose.css doesn't override em behavior). User-typed emphasis in
notes, journal entries, and chat messages keeps its italic.

Specific spots that lost the decorative italic:

- KnowledgeView .filter-label, .empty-narrator
- NoteEditorView .ef-label, link-suggest related
- ChatPanel .empty-msg, .empty-greeting, .role-label
- ChatMessage .role-assistant .role-label (the "Fable" voice tag —
  was italic per the doc's Illuminated Transcript spec, but per the
  new typography rule the speaker tag stays regular and lets the
  border-left + glow do the bubble framing on their own)
- AppHeader .brand-text ("Fabled" wordmark)
- editor-shared.css .title-input::placeholder
- ProjectView .project-title-input::placeholder
- HomeView .urgency-loading
- WorkspaceTaskPanel .empty-group
- WeatherCard .weather-unavailable
- SharedWithMeView .empty-msg
- DiffView empty-state spans
- ToolCallCard .tool-event-more
- SettingsView 7 spots (.you-label, .geo-pending, hint text, etc.)
- + several other empty-state / hint text spots

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 07:54:26 -04:00

175 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from "vue";
import type { DiffLine } from "@/composables/useAssist";
const props = defineProps<{
diff: DiffLine[];
}>();
const CONTEXT_LINES = 3;
const MIN_COLLAPSE = 6; // Only collapse if there are more than this many consecutive equal lines
type CollapseMarker = { type: 'collapse'; count: number };
type DisplayItem = DiffLine | CollapseMarker;
const insertions = computed(() => props.diff.filter(l => l.type === 'insert').length);
const deletions = computed(() => props.diff.filter(l => l.type === 'delete').length);
const displayItems = computed<DisplayItem[]>(() => {
const d = props.diff;
if (!d.length) return [];
// Mark which indices are within CONTEXT_LINES of a change
const shown = new Set<number>();
for (let i = 0; i < d.length; i++) {
if (d[i].type !== 'equal') {
for (let c = Math.max(0, i - CONTEXT_LINES); c <= Math.min(d.length - 1, i + CONTEXT_LINES); c++) {
shown.add(c);
}
}
}
// If nothing changed, show all
if (shown.size === 0) return d;
const result: DisplayItem[] = [];
let i = 0;
while (i < d.length) {
if (shown.has(i)) {
result.push(d[i]);
i++;
} else {
let count = 0;
while (i < d.length && !shown.has(i)) { count++; i++; }
if (count >= MIN_COLLAPSE) {
result.push({ type: 'collapse', count });
} else {
// Too short to collapse — show as-is
for (let k = i - count; k < i; k++) result.push(d[k]);
}
}
}
return result;
});
function markerFor(type: DiffLine['type']): string {
if (type === 'insert') return '+';
if (type === 'delete') return '';
return ' ';
}
</script>
<template>
<div class="diff-view-full">
<div class="diff-summary-bar">
<span class="diff-summary-ins">+{{ insertions }} insertion{{ insertions !== 1 ? 's' : '' }}</span>
<span class="diff-summary-del">{{ deletions }} deletion{{ deletions !== 1 ? 's' : '' }}</span>
</div>
<div class="diff-scroll">
<div v-if="!diff.length" class="diff-empty">No changes.</div>
<template v-for="(item, i) in displayItems" :key="i">
<div v-if="item.type === 'collapse'" class="diff-line diff-collapse">
<span class="diff-marker"></span>
<span class="diff-text">{{ item.count }} unchanged line{{ item.count !== 1 ? 's' : '' }}</span>
</div>
<div
v-else
:class="['diff-line', `diff-${item.type}`]"
>
<span class="diff-marker">{{ markerFor(item.type) }}</span>
<span class="diff-text">{{ item.text }}</span>
</div>
</template>
</div>
</div>
</template>
<style scoped>
.diff-view-full {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
border: 1px solid var(--color-input-border);
border-radius: var(--radius-sm);
background: var(--color-bg);
overflow: hidden;
}
.diff-summary-bar {
position: sticky;
top: 0;
flex-shrink: 0;
display: flex;
gap: 1rem;
padding: 0.4rem 0.75rem;
background: var(--color-bg-secondary);
border-bottom: 1px solid var(--color-border);
font-size: 0.78rem;
font-family: monospace;
font-weight: 600;
}
.diff-summary-ins { color: var(--color-success, #2ecc71); }
.diff-summary-del { color: var(--color-danger, #e74c3c); }
.diff-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0.3rem 0;
}
.diff-empty {
padding: 0.75rem;
font-size: 0.85rem;
color: var(--color-text-muted);
}
.diff-line {
display: flex;
align-items: baseline;
padding: 0.04rem 0.5rem;
line-height: 1.55;
font-family: monospace;
font-size: 0.82rem;
}
.diff-delete {
background: color-mix(in srgb, var(--color-danger, #e74c3c) 12%, transparent);
color: var(--color-danger, #e74c3c);
}
.diff-insert {
background: color-mix(in srgb, var(--color-success, #2ecc71) 12%, transparent);
color: var(--color-success, #2ecc71);
}
.diff-equal {
color: var(--color-text-muted);
}
.diff-collapse {
color: var(--color-text-muted);
opacity: 0.6;
border-top: 1px dashed var(--color-border);
border-bottom: 1px dashed var(--color-border);
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
.diff-marker {
flex-shrink: 0;
width: 1rem;
text-align: center;
user-select: none;
font-weight: 700;
}
.diff-text {
flex: 1;
white-space: pre-wrap;
word-break: break-word;
}
</style>