Fix settings toggles layout and add processing-log retention controls

This commit is contained in:
2026-02-21 12:09:01 -03:00
parent 4beab4bc09
commit a18545fb18
4 changed files with 165 additions and 8 deletions

View File

@@ -12,6 +12,7 @@ import type {
DisplaySettings,
HandwritingStyleClusteringSettings,
OcrTaskSettings,
ProcessingLogRetentionSettings,
PredefinedPathEntry,
PredefinedTagEntry,
ProviderSettings,
@@ -47,6 +48,24 @@ function parseCardsPerPageInput(input: string, fallback: number): number {
return clampCardsPerPage(parsed);
}
const DEFAULT_PROCESSING_LOG_RETENTION: ProcessingLogRetentionSettings = {
keep_document_sessions: 2,
keep_unbound_entries: 80,
};
const PROCESSING_LOG_SESSION_MIN = 0;
const PROCESSING_LOG_SESSION_MAX = 20;
const PROCESSING_LOG_UNBOUND_MIN = 0;
const PROCESSING_LOG_UNBOUND_MAX = 400;
function clampProcessingLogDocumentSessions(value: number): number {
return Math.max(PROCESSING_LOG_SESSION_MIN, Math.min(PROCESSING_LOG_SESSION_MAX, value));
}
function clampProcessingLogUnboundEntries(value: number): number {
return Math.max(PROCESSING_LOG_UNBOUND_MIN, Math.min(PROCESSING_LOG_UNBOUND_MAX, value));
}
/**
* Renders compact human-oriented settings controls.
*/
@@ -69,6 +88,7 @@ export default function SettingsScreen({
const [newPredefinedTag, setNewPredefinedTag] = useState<string>('');
const [uploadDefaults, setUploadDefaults] = useState<UploadDefaultsSettings | null>(null);
const [displaySettings, setDisplaySettings] = useState<DisplaySettings | null>(null);
const [processingLogRetention, setProcessingLogRetention] = useState<ProcessingLogRetentionSettings | null>(null);
const [cardsPerPageInput, setCardsPerPageInput] = useState<string>('12');
const [error, setError] = useState<string | null>(null);
@@ -92,6 +112,15 @@ export default function SettingsScreen({
setPredefinedTags(settings.predefined_tags);
setUploadDefaults(settings.upload_defaults);
setDisplaySettings(settings.display);
setProcessingLogRetention({
keep_document_sessions: clampProcessingLogDocumentSessions(
settings.processing_log_retention?.keep_document_sessions ??
DEFAULT_PROCESSING_LOG_RETENTION.keep_document_sessions,
),
keep_unbound_entries: clampProcessingLogUnboundEntries(
settings.processing_log_retention?.keep_unbound_entries ?? DEFAULT_PROCESSING_LOG_RETENTION.keep_unbound_entries,
),
});
setCardsPerPageInput(String(settings.display.cards_per_page));
setError(null);
}, [settings]);
@@ -163,7 +192,15 @@ export default function SettingsScreen({
};
const handleSave = useCallback(async (): Promise<void> => {
if (!ocrTask || !summaryTask || !routingTask || !handwritingStyle || !uploadDefaults || !displaySettings) {
if (
!ocrTask ||
!summaryTask ||
!routingTask ||
!handwritingStyle ||
!uploadDefaults ||
!displaySettings ||
!processingLogRetention
) {
setError('Settings are not fully loaded yet');
return;
}
@@ -175,7 +212,12 @@ export default function SettingsScreen({
setError(null);
try {
const resolvedCardsPerPage = parseCardsPerPageInput(cardsPerPageInput, displaySettings.cards_per_page);
const resolvedProcessingLogRetention: ProcessingLogRetentionSettings = {
keep_document_sessions: clampProcessingLogDocumentSessions(processingLogRetention.keep_document_sessions),
keep_unbound_entries: clampProcessingLogUnboundEntries(processingLogRetention.keep_unbound_entries),
};
setDisplaySettings({ ...displaySettings, cards_per_page: resolvedCardsPerPage });
setProcessingLogRetention(resolvedProcessingLogRetention);
setCardsPerPageInput(String(resolvedCardsPerPage));
await onSave({
@@ -187,6 +229,7 @@ export default function SettingsScreen({
cards_per_page: resolvedCardsPerPage,
log_typing_animation_enabled: displaySettings.log_typing_animation_enabled,
},
processing_log_retention: resolvedProcessingLogRetention,
predefined_paths: predefinedPaths,
predefined_tags: predefinedTags,
handwriting_style_clustering: {
@@ -252,21 +295,51 @@ export default function SettingsScreen({
routingTask,
summaryTask,
uploadDefaults,
processingLogRetention,
]);
useEffect(() => {
if (!onRegisterSaveAction) {
return;
}
if (!settings || !ocrTask || !summaryTask || !routingTask || !handwritingStyle || !uploadDefaults || !displaySettings) {
if (
!settings ||
!ocrTask ||
!summaryTask ||
!routingTask ||
!handwritingStyle ||
!uploadDefaults ||
!displaySettings ||
!processingLogRetention
) {
onRegisterSaveAction(null);
return;
}
onRegisterSaveAction(() => handleSave());
return () => onRegisterSaveAction(null);
}, [displaySettings, handleSave, handwritingStyle, ocrTask, onRegisterSaveAction, routingTask, settings, summaryTask, uploadDefaults]);
}, [
displaySettings,
handleSave,
handwritingStyle,
ocrTask,
onRegisterSaveAction,
processingLogRetention,
routingTask,
settings,
summaryTask,
uploadDefaults,
]);
if (!settings || !ocrTask || !summaryTask || !routingTask || !handwritingStyle || !uploadDefaults || !displaySettings) {
if (
!settings ||
!ocrTask ||
!summaryTask ||
!routingTask ||
!handwritingStyle ||
!uploadDefaults ||
!displaySettings ||
!processingLogRetention
) {
return (
<section className="settings-layout">
<div className="settings-card">
@@ -313,6 +386,42 @@ export default function SettingsScreen({
onChange={(event) => setCardsPerPageInput(event.target.value)}
/>
</label>
<label className="settings-field">
Keep document sessions
<input
type="number"
min={PROCESSING_LOG_SESSION_MIN}
max={PROCESSING_LOG_SESSION_MAX}
value={processingLogRetention.keep_document_sessions}
onChange={(event) => {
const nextValue = Number.parseInt(event.target.value, 10);
if (!Number.isNaN(nextValue)) {
setProcessingLogRetention({
...processingLogRetention,
keep_document_sessions: clampProcessingLogDocumentSessions(nextValue),
});
}
}}
/>
</label>
<label className="settings-field">
Keep unbound entries
<input
type="number"
min={PROCESSING_LOG_UNBOUND_MIN}
max={PROCESSING_LOG_UNBOUND_MAX}
value={processingLogRetention.keep_unbound_entries}
onChange={(event) => {
const nextValue = Number.parseInt(event.target.value, 10);
if (!Number.isNaN(nextValue)) {
setProcessingLogRetention({
...processingLogRetention,
keep_unbound_entries: clampProcessingLogUnboundEntries(nextValue),
});
}
}}
/>
</label>
<label className="inline-checkbox settings-checkbox-field">
<input
type="checkbox"
@@ -323,6 +432,9 @@ export default function SettingsScreen({
/>
Processing log typing animation enabled
</label>
<p className="small settings-helper-text">
Processing-log retention values are used by backend trim routines when pruning historical entries.
</p>
</div>
</div>

View File

@@ -74,7 +74,7 @@
min-height: 2rem;
}
input,
input:not([type='checkbox']):not([type='radio']),
select,
textarea {
width: 100%;
@@ -86,24 +86,39 @@ textarea {
transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
}
input::placeholder,
input:not([type='checkbox']):not([type='radio'])::placeholder,
textarea::placeholder {
color: #72819e;
}
input:hover,
input:not([type='checkbox']):not([type='radio']):hover,
select:hover,
textarea:hover {
border-color: var(--color-border-strong);
}
input:focus,
input:not([type='checkbox']):not([type='radio']):focus,
select:focus,
textarea:focus {
border-color: var(--color-accent);
box-shadow: 0 0 0 2px rgba(63, 141, 255, 0.2);
}
input[type='checkbox'],
input[type='radio'] {
width: 1rem;
height: 1rem;
margin: 0;
accent-color: var(--color-accent);
cursor: pointer;
}
input[type='checkbox']:focus-visible,
input[type='radio']:focus-visible {
outline: 2px solid rgba(63, 141, 255, 0.6);
outline-offset: 2px;
}
select {
appearance: none;
padding-right: 1.9rem;
@@ -966,12 +981,23 @@ button:disabled {
grid-template-columns: auto 1fr;
align-items: center;
gap: 0.45rem;
min-height: 1.95rem;
padding: 0.35rem 0.45rem;
border: 1px solid rgba(70, 89, 122, 0.55);
border-radius: var(--radius-xs);
background: rgba(18, 27, 41, 0.62);
font-size: 0.79rem;
color: #cad7ed;
cursor: pointer;
}
.inline-checkbox input {
margin: 0;
flex-shrink: 0;
}
.inline-checkbox input:disabled {
cursor: not-allowed;
}
.settings-toggle {

View File

@@ -176,6 +176,14 @@ export interface DisplaySettings {
log_typing_animation_enabled: boolean;
}
/**
* Represents retention targets used when trimming persisted processing logs.
*/
export interface ProcessingLogRetentionSettings {
keep_document_sessions: number;
keep_unbound_entries: number;
}
/**
* Represents one predefined logical path and discoverability scope.
*/
@@ -220,6 +228,7 @@ export interface TaskSettings {
export interface AppSettings {
upload_defaults: UploadDefaultsSettings;
display: DisplaySettings;
processing_log_retention: ProcessingLogRetentionSettings;
handwriting_style_clustering: HandwritingStyleClusteringSettings;
predefined_paths: PredefinedPathEntry[];
predefined_tags: PredefinedTagEntry[];
@@ -265,6 +274,14 @@ export interface DisplaySettingsUpdate {
log_typing_animation_enabled?: boolean;
}
/**
* Represents processing-log retention update payload.
*/
export interface ProcessingLogRetentionSettingsUpdate {
keep_document_sessions?: number;
keep_unbound_entries?: number;
}
/**
* Represents handwriting-style clustering settings update payload.
*/
@@ -284,6 +301,7 @@ export interface HandwritingStyleClusteringSettingsUpdate {
export interface AppSettingsUpdate {
upload_defaults?: UploadDefaultsSettingsUpdate;
display?: DisplaySettingsUpdate;
processing_log_retention?: ProcessingLogRetentionSettingsUpdate;
handwriting_style_clustering?: HandwritingStyleClusteringSettingsUpdate;
predefined_paths?: PredefinedPathEntry[];
predefined_tags?: PredefinedTagEntry[];