{}} />);
+ const control = screen.getByLabelText('Model for Claude');
+ expect(control.tagName).toBe('INPUT');
+ expect(control).toHaveValue('llama3.1:70b');
});
it('falls back to free-text when no options resolved (a closed empty select would be dead)', () => {
@@ -358,6 +397,7 @@ describe('ReviewerPicker', () => {
it('treats a whitespace-only entry as a clear, not a pin', () => {
const onChange = vi.fn();
render();
+ fireEvent.change(screen.getByLabelText('Model for Codex'), { target: { value: '[custom]' } });
fireEvent.change(screen.getByLabelText('Model for Codex'), { target: { value: ' ' } });
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ reviewerModels: {} }));
});
@@ -417,6 +457,7 @@ describe('ReviewerPicker', () => {
// `foo]~opt` would close the selector early and leave slashdo reading the
// rest as a suffix; the server drops such an id, so accepting it here would
// show a pin that never persists.
+ fireEvent.change(screen.getByLabelText('Model for Codex'), { target: { value: '[custom]' } });
fireEvent.change(screen.getByLabelText('Model for Codex'), { target: { value: 'foo]~opt' } });
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ reviewerModels: { codex: 'foo~opt' } }));
});
@@ -424,6 +465,7 @@ describe('ReviewerPicker', () => {
it('keeps a space in a typed id (slashdo selectors are free-form)', () => {
const onChange = vi.fn();
render();
+ fireEvent.change(screen.getByLabelText('Model for Claude'), { target: { value: '[custom]' } });
fireEvent.change(screen.getByLabelText('Model for Claude'), { target: { value: 'Some Model (High)' } });
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ reviewerModels: { claude: 'Some Model (High)' } }));
});
diff --git a/client/src/components/settings/CodeReviewersTab.jsx b/client/src/components/settings/CodeReviewersTab.jsx
index 794f655ece..c405ec97b3 100644
--- a/client/src/components/settings/CodeReviewersTab.jsx
+++ b/client/src/components/settings/CodeReviewersTab.jsx
@@ -105,7 +105,7 @@ export default function CodeReviewersTab() {
Code Review Defaults
- Default Review Loop reviewer chain — used by ad-hoc CoS tasks and task-type schedules that haven't pinned their own. Local-LLM reviewers route the diff through PortOS's local code-review endpoint; the {CLI_REVIEWER_LIST} reviewers invoke their CLI directly. Each runs the model pinned on its row (Claude also supports an Ollama-backed CLI for local-only setups — type one of your installed Ollama models).
+ Default Review Loop reviewer chain — used by ad-hoc CoS tasks and task-type schedules that haven't pinned their own. Leave it empty and reviews follow your default AI provider, at its own model and reasoning effort. Local-LLM reviewers route the diff through PortOS's local code-review endpoint; the {CLI_REVIEWER_LIST} reviewers invoke their CLI directly. Each runs the model picked on its row — choose Custom… to type an id its catalog doesn't list, such as an installed Ollama model for an Ollama-backed Claude.
{loadError && (
diff --git a/client/src/hooks/useReviewerModelOptions.js b/client/src/hooks/useReviewerModelOptions.js
index a4e7bba48d..b1e8b2de92 100644
--- a/client/src/hooks/useReviewerModelOptions.js
+++ b/client/src/hooks/useReviewerModelOptions.js
@@ -31,10 +31,12 @@ const PROBED_LOCAL_BACKENDS = LOCAL_LLM_BACKENDS.map((b) => b.id);
* the installed Ollama ids (an Ollama-backed `claude` CLI, where `--model` selects
* the local model). Deduped, order-preserving.
*
- * `freeText` marks a reviewer whose picker must accept a typed id, not just a
- * pick: an Ollama-backed `claude` can run any locally-installed id, and a
+ * `freeText` marks a reviewer whose picker must ALSO accept a typed id, not only
+ * a pick: an Ollama-backed `claude` can run any locally-installed id, and a
* Bedrock/Vertex install needs its environment's own id form, neither of which a
- * catalog can enumerate. Consumers render a `