diff --git a/doc/README.md b/doc/README.md
index 3521b19..8fcf45b 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -9,4 +9,4 @@ This directory contains technical documentation for DMS.
- `api-contract.md` - API endpoint contract grouped by route module, including settings and processing-log trim defaults
- `data-model-reference.md` - database entity definitions and lifecycle states
- `operations-and-configuration.md` - runtime operations, ports, volumes, and persisted settings configuration
-- `frontend-design-foundation.md` - frontend visual system, tokens, and UI implementation rules
+- `frontend-design-foundation.md` - frontend visual system, tokens, UI implementation rules, and settings helper-copy guidance
diff --git a/doc/frontend-design-foundation.md b/doc/frontend-design-foundation.md
index 0ccf488..ca52093 100644
--- a/doc/frontend-design-foundation.md
+++ b/doc/frontend-design-foundation.md
@@ -50,3 +50,10 @@ When adding or redesigning a UI area:
3. Implement component styles in `frontend/src/styles.css` using existing layout and variant conventions.
4. Validate responsive behavior at `1240px`, `1040px`, `760px`, and `560px` breakpoints.
5. Verify keyboard focus visibility and text contrast before merging.
+
+## Settings UX Copy
+
+- Keep helper copy in settings short and plain language, especially on advanced model and threshold controls.
+- Prefer one concise hint per advanced control that explains practical impact rather than internals.
+- In the Workspace settings block, keep processing-log controls visually separated from default path and tag behavior.
+- Processing-log hints must explicitly state they affect logs and retention behavior, not document metadata values.
diff --git a/frontend/src/components/SettingsScreen.tsx b/frontend/src/components/SettingsScreen.tsx
index 5b27bcd..fe52135 100644
--- a/frontend/src/components/SettingsScreen.tsx
+++ b/frontend/src/components/SettingsScreen.tsx
@@ -1,5 +1,6 @@
/**
* Dedicated settings screen for providers, task model bindings, and catalog controls.
+ * Uses concise helper hints for advanced runtime and provider settings.
*/
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { JSX } from 'react';
@@ -67,7 +68,7 @@ function clampProcessingLogUnboundEntries(value: number): number {
}
/**
- * Renders compact human-oriented settings controls.
+ * Renders compact human-oriented settings controls with plain-language hints.
*/
export default function SettingsScreen({
settings,
@@ -367,6 +368,7 @@ export default function SettingsScreen({
onChange={(nextPath) => setUploadDefaults({ ...uploadDefaults, logical_path: nextPath })}
suggestions={knownPaths}
/>
+ Used when you upload without choosing a path.
+
+
Processing Log Controls
+
+ These settings affect processing logs only. They do not change default path, tags, or document cards.
+
+
-
Label
@@ -596,6 +612,7 @@ export default function SettingsScreen({
);
}}
/>
+ Stop waiting after this many seconds if a call hangs.
Base URL
@@ -609,6 +626,7 @@ export default function SettingsScreen({
)
}
/>
+ API endpoint root for this provider.
API Key
@@ -624,6 +642,7 @@ export default function SettingsScreen({
)
}
/>
+ Leave blank to keep the current stored key.
Model
setOcrTask({ ...ocrTask, model: event.target.value })} />
+ Model name sent to the selected provider.
OCR Prompt
@@ -708,6 +729,7 @@ export default function SettingsScreen({
Model
setSummaryTask({ ...summaryTask, model: event.target.value })} />
+ Model name sent to the selected provider.
Max Input Tokens
@@ -723,10 +745,12 @@ export default function SettingsScreen({
}
}}
/>
+ Long inputs are trimmed to this size before summarizing.
Summary Prompt
@@ -753,42 +777,56 @@ export default function SettingsScreen({
Model
setRoutingTask({ ...routingTask, model: event.target.value })} />
+ Model name sent to the selected provider.
Neighbor Count
setRoutingTask({ ...routingTask, neighbor_count: Number.parseInt(event.target.value, 10) || routingTask.neighbor_count })} />
+ How many close matches to compare before routing.
Min Neighbor Similarity
setRoutingTask({ ...routingTask, neighbor_min_similarity: Number.parseFloat(event.target.value) || routingTask.neighbor_min_similarity })} />
+ Ignore neighbors below this match score.
Auto Apply Confidence
setRoutingTask({ ...routingTask, auto_apply_confidence_threshold: Number.parseFloat(event.target.value) || routingTask.auto_apply_confidence_threshold })} />
+ Minimum model confidence for automatic changes.
Auto Apply Neighbor Similarity
setRoutingTask({ ...routingTask, auto_apply_neighbor_similarity_threshold: Number.parseFloat(event.target.value) || routingTask.auto_apply_neighbor_similarity_threshold })} />
+ Minimum neighbor score for automatic changes.
-
+
setRoutingTask({ ...routingTask, neighbor_path_override_enabled: event.target.checked })} />
- Dominant neighbor path override enabled
+
+ Dominant neighbor path override enabled
+
+ If a strong top match disagrees with the model, use the top match path instead.
+
+
Override Min Similarity
setRoutingTask({ ...routingTask, neighbor_path_override_min_similarity: Number.parseFloat(event.target.value) || routingTask.neighbor_path_override_min_similarity })} />
+ Top neighbor must reach this score to override.
Override Min Gap
setRoutingTask({ ...routingTask, neighbor_path_override_min_gap: Number.parseFloat(event.target.value) || routingTask.neighbor_path_override_min_gap })} />
+ Top match must beat the second match by at least this gap.
Override Max LLM Confidence
setRoutingTask({ ...routingTask, neighbor_path_override_max_confidence: Number.parseFloat(event.target.value) || routingTask.neighbor_path_override_max_confidence })} />
+ Override only runs when model confidence is at or below this level.
Routing Prompt
@@ -805,26 +843,32 @@ export default function SettingsScreen({
Typesense Embedding Model Slug
setHandwritingStyle({ ...handwritingStyle, embed_model: event.target.value })} />
+ Embedding model used to compare handwriting style similarity.
Neighbor Limit
setHandwritingStyle({ ...handwritingStyle, neighbor_limit: Number.parseInt(event.target.value, 10) || handwritingStyle.neighbor_limit })} />
+ How many nearby samples to check during matching.
Match Min Similarity
setHandwritingStyle({ ...handwritingStyle, match_min_similarity: Number.parseFloat(event.target.value) || handwritingStyle.match_min_similarity })} />
+ Minimum similarity needed to treat two styles as a match.
Bootstrap Match Min Similarity
setHandwritingStyle({ ...handwritingStyle, bootstrap_match_min_similarity: Number.parseFloat(event.target.value) || handwritingStyle.bootstrap_match_min_similarity })} />
+ Stricter match score used only while bootstrapping new clusters.
Bootstrap Sample Size
setHandwritingStyle({ ...handwritingStyle, bootstrap_sample_size: Number.parseInt(event.target.value, 10) || handwritingStyle.bootstrap_sample_size })} />
+ Number of samples used to start each new style cluster.
Max Image Side (px)
setHandwritingStyle({ ...handwritingStyle, image_max_side: Number.parseInt(event.target.value, 10) || handwritingStyle.image_max_side })} />
+ Resizes large images to this limit before analysis.
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index af32913..54c8820 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -976,6 +976,32 @@ button:disabled {
color: var(--color-text-muted);
}
+.settings-field-hint {
+ margin: 0;
+ font-size: 0.72rem;
+ font-weight: 400;
+ color: #95a6c4;
+ line-height: 1.35;
+}
+
+.settings-subsection-divider {
+ grid-column: span 12;
+ display: grid;
+ gap: 0.2rem;
+ padding-top: 0.2rem;
+ border-top: 1px solid rgba(70, 89, 122, 0.55);
+}
+
+.settings-subsection-divider h4 {
+ margin: 0;
+ font-family: var(--font-display);
+ font-size: 0.82rem;
+}
+
+.settings-subsection-divider p {
+ margin: 0;
+}
+
.inline-checkbox {
display: grid;
grid-template-columns: auto 1fr;
@@ -1004,6 +1030,15 @@ button:disabled {
color: #dbe8ff;
}
+.settings-checkbox-with-hint {
+ align-items: start;
+}
+
+.settings-checkbox-copy {
+ display: grid;
+ gap: 0.15rem;
+}
+
.settings-catalog-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));