Skip to content

Commit c40df38

Browse files
simandebvuclaude
andauthored
Feat/preferences touch ups (#61)
* feat: enhance UI components with dark mode support and language preferences - Updated ExampleContentMenu, TextInputPanel, and various preference components to support dark mode styling. - Added target language selection functionality in WorkspacePage, allowing users to set and persist language preferences. - Enhanced FormatTransformService to handle target language in transformations. - Improved accessibility and visual consistency across components with updated styles for dark mode. - Refactored history storage to include target language metadata for processing history entries. * feat: integrate preferences storage subscription for language settings in WorkspacePage - Added subscription to preferences storage for target and favorite languages, allowing real-time updates to language settings. - Enhanced state management for language preferences, improving user experience and responsiveness in the WorkspacePage component. * refactor: simplify format selection logic in FormatSelector component - Streamlined the handleFormatToggle function to improve clarity and maintainability. - Consolidated format selection and deselection logic, ensuring at least one format is always selected. - Removed redundant checks and comments, enhancing code readability. * feat: enhance image extraction logic and add relative image source resolution - Introduced a new function to resolve relative image sources against the final URL, improving image extraction accuracy. - Updated the extractImages function to utilize the new resolution logic, ensuring proper handling of image sources. - Added a test case to verify the functionality of resolving relative image sources in the readability parser. - Refactored the PreviewTooltip component to improve accessibility and user interaction with enhanced event handling. * feat: enhance progress tracking and UI feedback in output components - Updated FormattedOutput and MultiFormatProgress components to provide clearer progress information, including current format processing status. - Refactored progress calculation logic to account for partial steps and improve accuracy in displaying completion percentages. - Enhanced user feedback in the WorkspacePage to reflect real-time processing updates for selected formats. - Simplified format selection logic in FormatQuickSelector to ensure at least one format is always selected while improving code readability. * feat: add scrollbar to prevent flicker during streaming - Updated index.css to force the scrollbar to always show, enhancing user experience during streaming by preventing layout shifts. * refactor: clean up console logs and improve UI consistency - Removed unnecessary console log statements from various components including TextInputPanel, FormatSection, and FormattedOutput to enhance code clarity. - Updated text in integration tests for better accuracy in progress indicators. - Refactored className formatting in WorkspacePage for improved readability and consistency in UI elements. * fix: update progress indicators in integration tests for accuracy - Adjusted progress indicator text in component integration tests to reflect the correct format being processed. - Enhanced clarity in test assertions for currently processing formats in FormattedOutput and MultiFormatProgress components. - Ensured consistency in progress tracking across various components to improve user feedback during processing. * refactor: streamline chunk processing in FormatTransformService - Removed unnecessary chunk count tracking in the transformToFormatStreaming method to simplify the code. - Enhanced clarity in the chunk processing logic for improved maintainability. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b969c9a commit c40df38

20 files changed

Lines changed: 627 additions & 399 deletions

src/__tests__/integration/component-integration.test.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,11 @@ describe('Component Integration Tests', () => {
9696
/>
9797
)
9898

99-
// Should show progress indicator
100-
expect(screen.getByText(/Generating Formats \(/)).toBeInTheDocument()
101-
expect(screen.getByText(/1\/3/)).toBeInTheDocument()
99+
// Should show progress indicator (bullets complete, so showing format 2 of 3)
100+
expect(screen.getByText(/Generating Formats \(Format 2 of 3\)/)).toBeInTheDocument()
102101

103102
// Should show currently processing format
104-
expect(screen.getByText(/Currently processing: paragraphs/)).toBeInTheDocument()
103+
expect(screen.getByText(/Currently processing:/)).toBeInTheDocument()
105104
})
106105

107106
it('should handle clear functionality', async () => {

src/components/input/ExampleContentMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export const ExampleContentMenu = ({
150150
aria-controls={isOpen ? menuId : undefined}
151151
>
152152
<svg
153-
className="h-5 w-5"
153+
className="h-5 w-5 text-current"
154154
fill="none"
155155
viewBox="0 0 24 24"
156156
stroke="currentColor"
@@ -163,7 +163,7 @@ export const ExampleContentMenu = ({
163163
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
164164
/>
165165
</svg>
166-
<span>Load example</span>
166+
<span className="text-current">Load example</span>
167167
<svg
168168
className={`h-4 w-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
169169
fill="none"

src/components/input/TextInputPanel.tsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface TextInputPanelProps {
2525
onProcessingError?: (error: Error) => void
2626
initialText?: string
2727
selectedFormats?: FormatType[]
28+
targetLanguage?: string
2829
onInputStateChange?: (hasText: boolean) => void
2930
onProcessingStart?: () => void
3031
onOperationProgress?: (progress: OperationProgress | null) => void
@@ -41,6 +42,7 @@ export const TextInputPanel = ({
4142
onProcessingError,
4243
initialText = '',
4344
selectedFormats: propSelectedFormats,
45+
targetLanguage,
4446
onInputStateChange,
4547
onProcessingStart,
4648
onOperationProgress,
@@ -97,9 +99,12 @@ export const TextInputPanel = ({
9799
onInputStateChange?.(text.trim().length > 0)
98100
}, [text, onInputStateChange])
99101

102+
const preferenceSnapshot = loadPreferences()
103+
100104
// Format transform hook - auto-initialize with reading level
101105
const formatTransform = useFormatTransform(true, {
102-
readingLevel: loadPreferences().readingLevel || undefined,
106+
readingLevel: preferenceSnapshot.readingLevel || undefined,
107+
targetLanguage: targetLanguage || preferenceSnapshot.targetLanguage || undefined,
103108
})
104109

105110
// Pass progress updates to parent
@@ -203,13 +208,18 @@ export const TextInputPanel = ({
203208

204209
// Store the original text before processing
205210
const originalTextSnapshot = text
211+
const preferences = loadPreferences()
212+
const readingLevelPreference = preferences.readingLevel || undefined
213+
const effectiveTargetLanguage =
214+
targetLanguage || preferences.targetLanguage || undefined
206215

207216
try {
208217
// Check if format transform service is ready
209218
if (!formatTransform.isReady) {
210219
setStatus('initializing')
211220
await formatTransform.initialize({
212-
readingLevel: loadPreferences().readingLevel || undefined,
221+
readingLevel: readingLevelPreference,
222+
targetLanguage: effectiveTargetLanguage,
213223
})
214224
}
215225

@@ -223,7 +233,8 @@ export const TextInputPanel = ({
223233
text,
224234
selectedFormats[0],
225235
{
226-
readingLevel: loadPreferences().readingLevel || undefined,
236+
readingLevel: readingLevelPreference,
237+
targetLanguage: effectiveTargetLanguage,
227238
}
228239
)
229240

@@ -238,18 +249,15 @@ export const TextInputPanel = ({
238249
text,
239250
selectedFormats,
240251
(format, content, isComplete) => {
241-
console.log('[TextInputPanel] Streaming callback:', format, 'isComplete:', isComplete, 'content length:', content.length)
242252
if (isComplete) {
243253
aggregatedResults[format] = content
244-
console.log('[TextInputPanel] ✅ Format SAVED to aggregatedResults:', format, 'Content length:', content.length)
245-
} else {
246-
console.log('[TextInputPanel] ⏳ Format streaming (not saved yet):', format)
247254
}
248255
// Pass streaming updates to parent
249256
onStreamingUpdate?.(format, content, isComplete)
250257
},
251258
{
252-
readingLevel: loadPreferences().readingLevel || undefined,
259+
readingLevel: readingLevelPreference,
260+
targetLanguage: effectiveTargetLanguage,
253261
}
254262
)
255263

@@ -506,13 +514,12 @@ export const TextInputPanel = ({
506514
<div className="ml-auto flex flex-col items-end gap-1">
507515
<div className="flex items-center gap-2 text-sm text-neutral-600">
508516
<span
509-
className={`h-2.5 w-2.5 rounded-full ${
510-
formatTransform.availability === 'available' || formatTransform.availability === 'readily'
511-
? 'bg-green-500'
512-
: formatTransform.availability === 'downloadable'
513-
? 'bg-yellow-500 animate-pulse'
514-
: 'bg-red-500'
515-
}`}
517+
className={`h-2.5 w-2.5 rounded-full ${formatTransform.availability === 'available' || formatTransform.availability === 'readily'
518+
? 'bg-green-500'
519+
: formatTransform.availability === 'downloadable'
520+
? 'bg-yellow-500 animate-pulse'
521+
: 'bg-red-500'
522+
}`}
516523
aria-hidden="true"
517524
/>
518525
<span>

src/components/output/FormatSection.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,10 @@ const FormatSectionComponent = ({
5858
onToggle,
5959
onCopy,
6060
}: FormatSectionProps) => {
61-
console.log(`[FormatSection ${format}] RENDER - content length: ${content?.length || 0}, isLoading: ${isLoading}, isExpanded: ${isExpanded}`)
6261

6362
const [copySuccess, setCopySuccess] = useState(false)
6463
const formatConfig = useMemo(() => getFormatConfig(format), [format])
6564

66-
console.log(`[FormatSection ${format}] formatConfig:`, formatConfig ? 'found' : 'NOT FOUND')
6765

6866
const handleCopy = useCallback(async () => {
6967
onCopy()
@@ -130,7 +128,7 @@ const FormatSectionComponent = ({
130128
<path
131129
className="opacity-75"
132130
fill="currentColor"
133-
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 714 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
131+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 0 1 4 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
134132
/>
135133
</svg>
136134
<span className="font-medium">Processing...</span>
@@ -155,9 +153,8 @@ const FormatSectionComponent = ({
155153
</div>
156154
) : (
157155
<svg
158-
className={`h-5 w-5 text-neutral-400 transition-transform dark:text-neutral-500 ${
159-
isExpanded ? 'rotate-180' : ''
160-
}`}
156+
className={`h-5 w-5 text-neutral-400 transition-transform dark:text-neutral-500 ${isExpanded ? 'rotate-180' : ''
157+
}`}
161158
fill="none"
162159
viewBox="0 0 24 24"
163160
stroke="currentColor"

src/components/output/FormattedOutput.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ describe('FormattedOutput', () => {
113113
isLoading={isLoading}
114114
/>
115115
)
116-
expect(screen.getByText(/1\/3/)).toBeInTheDocument()
116+
// 1 complete, so showing Format 2 of 3
117+
expect(screen.getByText(/Format 2 of 3/)).toBeInTheDocument()
117118
})
118119

119120
it('should show currently processing format', () => {

src/components/output/FormattedOutput.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,21 +161,26 @@ export const FormattedOutput = ({
161161

162162
const hasLoadingInfo = Boolean(isLoading && isLoading.size > 0)
163163
const isInProgress = hasLoadingInfo && completed < total
164-
const percent = total > 0 ? (completed / total) * 100 : 0
164+
const partialStep = isInProgress && active ? 0.5 : 0
165+
const percent = total > 0 ? ((completed + partialStep) / total) * 100 : 0
166+
const currentPosition = isInProgress
167+
? Math.min(completed + 1, total)
168+
: total
165169

166170
return {
167171
total,
168172
completed,
169173
active: isInProgress ? active : undefined,
170174
isInProgress,
175+
currentPosition,
171176
percent,
172177
}
173178
}, [formats, isLoading, results])
174179

175180
const formatCountLabel = useMemo(() => {
176181
const suffix = progress.total !== 1 ? 's' : ''
177182
if (progress.isInProgress) {
178-
return `${progress.completed} of ${progress.total} format${suffix} complete`
183+
return `Currently processing format ${progress.currentPosition} of ${progress.total}`
179184
}
180185
return `${progress.total} format${suffix} generated`
181186
}, [progress])
@@ -207,7 +212,7 @@ export const FormattedOutput = ({
207212
</svg>
208213
<div className="flex-1">
209214
<p className="font-semibold text-blue-900 dark:text-blue-200">
210-
Generating Formats ({progress.completed}/{progress.total})
215+
Generating Formats (Format {progress.currentPosition} of {progress.total})
211216
</p>
212217
<p className="mt-1 text-sm text-blue-800 dark:text-blue-200/80">
213218
{progress.active && `Currently processing: ${getFormatLabels([progress.active])}`}
@@ -404,8 +409,6 @@ export const FormattedOutput = ({
404409
const isFormatLoading = isLoading?.get(format) ?? false
405410
const isFormatExpanded = expandedFormats.has(format)
406411

407-
console.log(`[FormattedOutput RENDER FormatSection] format: ${format}, content length: ${content.length}, isLoading: ${isFormatLoading}, isExpanded: ${isFormatExpanded}`)
408-
409412
return (
410413
<FormatSection
411414
key={format}

src/components/preferences/FormatPresetSelector.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ export const FormatPresetSelector = ({
5252
<div className="grid gap-4">
5353
<div className="flex items-center justify-between">
5454
<div>
55-
<h3 className="text-sm font-semibold text-neutral-900">Quick Presets</h3>
56-
<p className="text-xs text-neutral-600">Common format combinations</p>
55+
<h3 className="text-sm font-semibold text-neutral-900 dark:text-neutral-100">Quick Presets</h3>
56+
<p className="text-xs text-neutral-600 dark:text-neutral-400">Common format combinations</p>
5757
</div>
5858
{selectedPresetId && (
5959
<button
6060
type="button"
6161
onClick={() => onSelectPreset(null)}
6262
disabled={disabled}
63-
className="text-xs font-medium text-synapse-600 transition hover:text-synapse-700 disabled:cursor-not-allowed disabled:opacity-50"
63+
className="text-xs font-medium text-synapse-600 transition hover:text-synapse-700 disabled:cursor-not-allowed disabled:opacity-50 dark:text-synapse-300 dark:hover:text-synapse-200"
6464
>
6565
Clear preset
6666
</button>
@@ -78,8 +78,8 @@ export const FormatPresetSelector = ({
7878
disabled={disabled}
7979
className={`rounded-xl border px-4 py-3 text-left transition hc-surface ${
8080
isSelected
81-
? 'border-synapse-500 bg-synapse-50 shadow-soft hc-surface--active'
82-
: 'border-neutral-200 hover:border-neutral-300 hover:bg-neutral-50'
81+
? 'border-synapse-500 bg-synapse-50 shadow-soft hc-surface--active dark:border-synapse-400 dark:bg-synapse-900/30'
82+
: 'border-neutral-200 hover:border-neutral-300 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:border-neutral-500 dark:hover:bg-neutral-800/60'
8383
} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}
8484
aria-pressed={isSelected}
8585
>
@@ -88,7 +88,9 @@ export const FormatPresetSelector = ({
8888
<div className="mt-0.5 flex-shrink-0">
8989
<svg
9090
className={`h-5 w-5 ${
91-
isSelected ? 'text-synapse-600 hc-icon-accent' : 'text-neutral-500'
91+
isSelected
92+
? 'text-synapse-600 hc-icon-accent dark:text-synapse-200'
93+
: 'text-neutral-500 dark:text-neutral-400'
9294
}`}
9395
fill="none"
9496
viewBox="0 0 24 24"
@@ -100,14 +102,14 @@ export const FormatPresetSelector = ({
100102
</div>
101103
<div className="flex-1 min-w-0">
102104
<div className="flex items-center gap-2">
103-
<h4 className="text-sm font-semibold text-neutral-900">{preset.label}</h4>
105+
<h4 className="text-sm font-semibold text-neutral-900 dark:text-neutral-100">{preset.label}</h4>
104106
{isSelected && (
105-
<span className="rounded-full bg-synapse-100 px-2 py-0.5 text-xs font-medium text-synapse-700 hc-chip hc-chip--fill">
107+
<span className="rounded-full bg-synapse-100 px-2 py-0.5 text-xs font-medium text-synapse-700 hc-chip hc-chip--fill dark:bg-synapse-900/50 dark:text-synapse-200">
106108
Active
107109
</span>
108110
)}
109111
</div>
110-
<p className="mt-1 text-xs text-neutral-600">{preset.description}</p>
112+
<p className="mt-1 text-xs text-neutral-600 dark:text-neutral-400">{preset.description}</p>
111113
</div>
112114
</div>
113115
</button>

src/components/preferences/FormatPreviewCard.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ export const FormatPreviewCard = ({
6464
disabled={disabled}
6565
className={`flex h-full flex-col items-start rounded-2xl border px-5 py-4 text-left transition hc-surface ${
6666
isSelected
67-
? 'border-primary-600 bg-primary-50 shadow-[0_0_0_4px_rgba(59,130,246,0.15)] hc-surface--active'
68-
: 'border-neutral-200 hover:border-neutral-300 hover:bg-neutral-50'
67+
? 'border-primary-600 bg-primary-50 shadow-[0_0_0_4px_rgba(59,130,246,0.15)] hc-surface--active dark:border-primary-400 dark:bg-primary-900/30 dark:shadow-[0_0_0_4px_rgba(56,189,248,0.25)]'
68+
: 'border-neutral-200 hover:border-neutral-300 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:border-neutral-500 dark:hover:bg-neutral-800/60'
6969
} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}
7070
aria-pressed={isSelected}
7171
>
@@ -76,13 +76,15 @@ export const FormatPreviewCard = ({
7676
<div
7777
className={`rounded-lg p-2 ${
7878
isSelected
79-
? 'bg-primary-100 hc-chip hc-chip--fill'
80-
: 'bg-neutral-100 hc-chip'
79+
? 'bg-primary-100 hc-chip hc-chip--fill dark:bg-primary-900/50'
80+
: 'bg-neutral-100 hc-chip dark:bg-neutral-800/70'
8181
}`}
8282
>
8383
<svg
8484
className={`h-5 w-5 ${
85-
isSelected ? 'text-primary-600 hc-icon-accent' : 'text-neutral-600'
85+
isSelected
86+
? 'text-primary-600 hc-icon-accent dark:text-primary-200'
87+
: 'text-neutral-600 dark:text-neutral-300'
8688
}`}
8789
fill="none"
8890
viewBox="0 0 24 24"
@@ -93,9 +95,9 @@ export const FormatPreviewCard = ({
9395
</svg>
9496
</div>
9597
<div>
96-
<h3 className="text-base font-semibold text-neutral-900">{config.label}</h3>
98+
<h3 className="text-base font-semibold text-neutral-900 dark:text-neutral-100">{config.label}</h3>
9799
{isSelected && (
98-
<span className="text-xs font-medium text-primary-600 hc-text-accent">
100+
<span className="text-xs font-medium text-primary-600 hc-text-accent dark:text-primary-200">
99101
Selected
100102
</span>
101103
)}
@@ -119,14 +121,14 @@ export const FormatPreviewCard = ({
119121
</div>
120122

121123
{/* Description */}
122-
<p className="mt-3 text-sm text-neutral-600">{config.description}</p>
124+
<p className="mt-3 text-sm text-neutral-600 dark:text-neutral-300">{config.description}</p>
123125

124126
{/* Preview */}
125-
<div className="mt-4 w-full rounded-lg border border-neutral-200 bg-white p-3 hc-surface-subtle">
126-
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-500 hc-text-accent">
127+
<div className="mt-4 w-full rounded-lg border border-neutral-200 bg-white p-3 hc-surface-subtle dark:border-neutral-700 dark:bg-neutral-900/40">
128+
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-500 hc-text-accent dark:text-neutral-400">
127129
Preview
128130
</p>
129-
<pre className="whitespace-pre-wrap font-sans text-xs text-neutral-700">
131+
<pre className="whitespace-pre-wrap font-sans text-xs text-neutral-700 dark:text-neutral-200">
130132
{config.preview}
131133
</pre>
132134
</div>

0 commit comments

Comments
 (0)