Skip to content

fix: settings page UX polish -- toggle bug, source badges, form improvements#712

Merged
Aureliolo merged 7 commits intomainfrom
feat/improve-settings
Mar 22, 2026
Merged

fix: settings page UX polish -- toggle bug, source badges, form improvements#712
Aureliolo merged 7 commits intomainfrom
feat/improve-settings

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Fix advanced toggle not reverting visually on Cancel (local ref buffer + @cancel handler)
  • Normalize dashboard System Status casing ("ok" -> "OK" everywhere)
  • Make department card edit/delete buttons visible (outlined instead of invisible text)
  • Grey out environment-sourced settings with amber message instead of just a badge
  • Add agent dropdown to department form head field, help text on JSON accordion sections
  • Use SettingGroupRenderer for company General tab (grouped layout with section headers)
  • Hide setup_complete from settings UI (ADVANCED level + HIDDEN_SETTINGS filter)
  • Rename source badges: "Database" -> "Custom", "YAML" -> "Config File"
  • Add enum Select placeholder showing definition default when value is empty
  • Remove unused default_provider setting (definition + subscriber + tests)
  • Make Department.head optional in backend model -- departments always show in Org Chart regardless, with "No head assigned" warning when missing

Test plan

  • Settings page: toggle advanced ON -> cancel dialog -> verify toggle reverts
  • Settings page: check no setup_complete in API tab (basic mode)
  • Settings page: source badges show "Custom" / "Config File" / "Default"
  • Settings page: env-sourced fields greyed out with amber message
  • Settings page: enum Select shows placeholder when value empty
  • Company > General tab: grouped layout with section headers
  • Company > Departments: edit/delete buttons visible on cards
  • Company > Departments: "No head assigned" warning for headless departments
  • Company > Departments > Add: head is agent dropdown, help text on JSON sections
  • Dashboard: all statuses show uppercase "OK"
  • Providers tab: no default_provider setting
  • Org Chart: departments appear regardless of head being set
  • Ruff + mypy + pytest (10,273 passed, 93.74% coverage)
  • ESLint + vue-tsc + vitest (768 passed)

Review coverage

Pre-reviewed by 6 agents (docs-consistency, frontend-reviewer, conventions-enforcer, test-quality, code-reviewer, issue-verifier). 0 findings requiring fixes across all agents. Issue verifier flagged Department.head requirement as the root cause of Org Chart empty state -- addressed in second commit.

Closes #709

Generated with Claude Code

Aureliolo and others added 2 commits March 22, 2026 12:50
…vements (#709)

Fix advanced toggle not reverting on cancel, normalize dashboard status
casing, make department card buttons visible, grey out env-sourced settings,
add agent dropdown to department form, use grouped layout for company general
tab, hide setup_complete from UI, rename source badges to user-friendly
labels, remove unused default_provider setting, and make department head
required to align with backend schema.

Closes #709

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Backend Department model now allows head=None instead of requiring it.
Departments without a head assigned show a warning in the UI card
rather than being silently filtered from the Org Chart. The department
form keeps the agent dropdown UX but head is no longer required.

Pre-reviewed by 6 agents (docs-consistency, frontend-reviewer,
conventions-enforcer, test-quality, code-reviewer, issue-verifier).
17 findings addressed across all agents.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 22, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8bc2016b-e135-4ade-a45a-e65a5af0001b

📥 Commits

Reviewing files that changed from the base of the PR and between 1d559c9 and 711287a.

📒 Files selected for processing (1)
  • web/src/components/company/CompanyDepartmentFormDialog.vue

Walkthrough

Department `head` is now optional (`None`) and hierarchy construction omits inferring team-lead→department-head links for headless departments. The `providers.default_provider` setting and its watcher were removed. `api/setup_complete` was set to advanced and is filtered from the UI. Frontend changes: source badge labels updated, environment-sourced settings render read-only with an explanatory message, hidden settings are excluded from renderers, `SystemStatus` uppercases status, department forms use a nullable `Select` for `head` and show “No head assigned,” and department card action buttons use outlined styling. Tests and constants updated accordingly.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: settings UI polish, toggle bug fix, and form improvements, which align with the changeset.
Description check ✅ Passed The description is well-structured and directly related to the changeset, detailing the specific fixes, improvements, and test coverage for all modified components.
Linked Issues check ✅ Passed The PR addresses all 12 objectives from issue #709: toggle cancel handling, optional Department.head, visible action buttons, System Status casing, env-sourced field disabling, agent dropdown, grouped General tab, hidden setup_complete, renamed badges, default_provider removal, and related test/backend updates.
Out of Scope Changes check ✅ Passed All changes remain within the scope of issue #709: settings UI polish, company page improvements, backend model updates for Department.head, and related test coverage. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 61.11% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a comprehensive set of user experience and functional improvements across the application, primarily focusing on the settings page and department management. It addresses several UI inconsistencies and bugs, enhances clarity for users regarding setting sources and system status, and refines backend models to support more flexible organizational structures. The changes aim to make the application's configuration and organizational tools more intuitive and robust.

Highlights

  • Settings Page UX Improvements: Fixed the advanced toggle not visually reverting on 'Cancel' and implemented a local reference buffer for visual consistency. Environment-sourced settings are now greyed out with an amber message, and enum Select components display a default placeholder when empty.
  • Department Management Enhancements: Made the Department.head field optional in the backend model, ensuring departments always appear in the Org Chart. Department cards now display 'No head assigned' if applicable, and edit/delete buttons are visibly outlined. The department form's head field is now an agent dropdown, and JSON accordion sections include helpful text.
  • System Status and Setting Source Clarity: Normalized dashboard System Status casing to 'OK' everywhere. Setting source badges have been renamed for clarity: 'Database' is now 'Custom' and 'YAML' is 'Config File'.
  • Backend Setting Refinements: The setup_complete setting is now hidden from the settings UI by marking it as an advanced level setting and filtering it out. The unused default_provider setting, along with its definition, subscriber, and tests, has been removed.
  • UI Component Usage: The company General tab now utilizes SettingGroupRenderer for a more organized, grouped layout with section headers.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 22, 2026 11:55 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/settings/SettingField.vue (1)

152-235: ⚠️ Potential issue | 🟠 Major

Env-sourced settings are still saveable through the JSON format path.

The UI presents env entries as read-only, but Format JSON can still mutate localValue, and canSave currently allows saving when dirty. This breaks the non-editable contract for env-sourced settings.

🔧 Proposed fix
-const canSave = computed(() => isDirty.value && validationError.value === null && !props.saving)
+const canSave = computed(
+  () => !isEnvSourced.value && isDirty.value && validationError.value === null && !props.saving,
+)

 function formatJson() {
+  if (isEnvSourced.value) return
   try {
     const parsed = JSON.parse(localValue.value)
     localValue.value = JSON.stringify(parsed, null, 2)
   } catch {
     // Invalid JSON -- do nothing
   }
 }
         <Button
           label="Format JSON"
           text
           size="small"
           class="mt-1"
+          :disabled="saving || isEnvSourced"
           `@click`="formatJson"
         />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/settings/SettingField.vue` around lines 152 - 235, The
JSON editing path currently allows mutation for env-sourced settings; update the
UI and save logic so env-sourced entries are truly read-only: when isEnvSourced
is true, disable or set the JSON Textarea (the element bound to localValue under
def.type === 'json') and also short-circuit the save guard (canSave) to return
false if isEnvSourced, ensuring no save action can run for env-provided keys;
locate references to localValue, the JSON Textarea block, and canSave to apply
these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/utils/constants.ts`:
- Around line 110-113: HIDDEN_SETTINGS is currently exported as a mutable Set;
change it to export a ReadonlySet backed by an internal constant to prevent
runtime mutation. Create an internal const (e.g., _HIDDEN_SETTINGS or
HIDDEN_SETTINGS_SET) initialized with the strings, then export export const
HIDDEN_SETTINGS: ReadonlySet<string> = _HIDDEN_SETTINGS so consumers get a
read-only view while the module retains the original Set for any internal
checks.

---

Outside diff comments:
In `@web/src/components/settings/SettingField.vue`:
- Around line 152-235: The JSON editing path currently allows mutation for
env-sourced settings; update the UI and save logic so env-sourced entries are
truly read-only: when isEnvSourced is true, disable or set the JSON Textarea
(the element bound to localValue under def.type === 'json') and also
short-circuit the save guard (canSave) to return false if isEnvSourced, ensuring
no save action can run for env-provided keys; locate references to localValue,
the JSON Textarea block, and canSave to apply these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 385fef30-a92d-47c2-ad70-efeb5b895334

📥 Commits

Reviewing files that changed from the base of the PR and between dc6bd3f and fed9aec.

📒 Files selected for processing (20)
  • src/synthorg/communication/delegation/hierarchy.py
  • src/synthorg/core/company.py
  • src/synthorg/settings/definitions/api.py
  • src/synthorg/settings/definitions/providers.py
  • src/synthorg/settings/subscribers/provider_subscriber.py
  • tests/unit/settings/test_provider_subscriber.py
  • web/src/__tests__/components/SystemStatus.test.ts
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/components/company/CompanyDepartmentCard.vue
  • web/src/components/company/CompanyDepartmentFormDialog.vue
  • web/src/components/company/CompanyGeneralForm.vue
  • web/src/components/dashboard/SystemStatus.vue
  • web/src/components/settings/SettingField.vue
  • web/src/components/settings/SettingGroupRenderer.vue
  • web/src/components/settings/SettingSourceBadge.vue
  • web/src/utils/constants.ts
  • web/src/views/CompanyPage.vue
  • web/src/views/SettingsPage.vue
💤 Files with no reviewable changes (2)
  • src/synthorg/settings/definitions/providers.py
  • tests/unit/settings/test_provider_subscriber.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
web/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Vue 3 components must use TypeScript with type safety in the dashboard

Files:

  • web/src/__tests__/components/SystemStatus.test.ts
  • web/src/components/dashboard/SystemStatus.vue
  • web/src/views/CompanyPage.vue
  • web/src/utils/constants.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/components/company/CompanyDepartmentCard.vue
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/components/settings/SettingSourceBadge.vue
  • web/src/components/settings/SettingField.vue
  • web/src/components/company/CompanyGeneralForm.vue
  • web/src/components/settings/SettingGroupRenderer.vue
  • web/src/views/SettingsPage.vue
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/components/company/CompanyDepartmentFormDialog.vue
web/src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for Vue linting in the dashboard and run lint checks via npm --prefix web run lint

Files:

  • web/src/__tests__/components/SystemStatus.test.ts
  • web/src/components/dashboard/SystemStatus.vue
  • web/src/views/CompanyPage.vue
  • web/src/utils/constants.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/components/company/CompanyDepartmentCard.vue
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/components/settings/SettingSourceBadge.vue
  • web/src/components/settings/SettingField.vue
  • web/src/components/company/CompanyGeneralForm.vue
  • web/src/components/settings/SettingGroupRenderer.vue
  • web/src/views/SettingsPage.vue
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/components/company/CompanyDepartmentFormDialog.vue
web/src/__tests__/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vitest for Vue component unit tests with fast-check for property-based testing

Files:

  • web/src/__tests__/components/SystemStatus.test.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/__tests__/components/settings/SettingField.test.ts
web/**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use fast-check for property-based testing in Vue tests with fc.assert + fc.property

Files:

  • web/src/__tests__/components/SystemStatus.test.ts
  • web/src/__tests__/components/settings/SettingGroupRenderer.test.ts
  • web/src/__tests__/components/settings/SettingSourceBadge.test.ts
  • web/src/__tests__/components/settings/SettingField.test.ts
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python code -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for exception handling on Python 3.14 -- enforced by ruff
Line length must be 88 characters -- enforced by ruff

Files:

  • src/synthorg/core/company.py
  • src/synthorg/communication/delegation/hierarchy.py
  • src/synthorg/settings/subscribers/provider_subscriber.py
  • src/synthorg/settings/definitions/api.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints and Google-style docstrings -- enforced by ruff D rules and mypy strict mode
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic collections, use copy.deepcopy() at construction and MappingProxyType for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves
Use @computed_field instead of storing redundant fields; use NotBlankStr for all identifier/name fields instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code instead of bare create_task
Functions must be < 50 lines, files < 800 lines
All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO
Every module with business logic MUST import logger via from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging or print() in application code
Use event name constants from synthorg.observability.events.<domain> modules instead of string literals (e.g., API_REQUEST_STARTED from events.api)
Always use logger.info(EVENT, key=value) for structured logging -- never use logger.info('msg %s', val) format strings
Validate input at system boundaries (user input, external APIs, config files) rather than throughout application code
Handle errors explicitly and never silently swallow them
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples -- use generic names like example-provider, example-large-001, test-provider, test-small-001
Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for Pydantic models with dict/list fields

Files:

  • src/synthorg/core/company.py
  • src/synthorg/communication/delegation/hierarchy.py
  • src/synthorg/settings/subscribers/provider_subscriber.py
  • src/synthorg/settings/definitions/api.py
src/synthorg/{tools,core}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement

Files:

  • src/synthorg/core/company.py
🧠 Learnings (3)
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/components/SystemStatus.test.ts
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/views/CompanyPage.vue
  • web/src/components/company/CompanyDepartmentFormDialog.vue
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/settings/subscribers/provider_subscriber.py
🧬 Code graph analysis (2)
web/src/__tests__/components/SystemStatus.test.ts (2)
web/src/api/types.ts (1)
  • HealthStatus (599-605)
src/synthorg/api/controllers/health.py (1)
  • HealthStatus (31-54)
src/synthorg/settings/definitions/api.py (2)
web/src/api/types.ts (1)
  • SettingLevel (923-923)
src/synthorg/settings/enums.py (1)
  • SettingLevel (39-47)
🔇 Additional comments (25)
src/synthorg/settings/subscribers/provider_subscriber.py (1)

33-36: Docstring now correctly matches runtime behavior.

The updated wording accurately reflects that retry_max_attempts is advisory-only and only logged when changed, without triggering router rebuild.

web/src/views/SettingsPage.vue (3)

41-46: Local toggle buffer is correctly synchronized with store state.

This is a solid fix for ToggleSwitch visual desync and keeps UI state aligned with settingsStore.showAdvanced.


88-90: Warning/cancel branches now correctly reset visual toggle state.

Resetting advancedToggleValue in both paths addresses the “stuck-on” visual behavior when advanced mode is not confirmed.

Also applies to: 98-100


203-205: Template event wiring matches the new buffered-toggle flow.

Binding ToggleSwitch to the local ref and handling dialog @cancel closes the UX gap described in the PR objective.

Also applies to: 363-363

web/src/components/dashboard/SystemStatus.vue (1)

25-25: LGTM!

The uppercase transformation correctly normalizes status display casing (e.g., 'ok''OK', 'degraded''DEGRADED') as intended by the PR objectives. The conditional logic properly handles the null case with "Unreachable".

web/src/__tests__/components/SystemStatus.test.ts (1)

29-42: LGTM!

Tests correctly updated to assert the new uppercase status behavior ('OK' and 'DEGRADED'), matching the component's toUpperCase() transformation. The test names and assertions are properly aligned with the implementation.

src/synthorg/core/company.py (1)

271-271: LGTM!

Making Department.head optional with NotBlankStr | None is the correct approach — when present, the value must still be non-blank. The docstring attribute description is updated appropriately, and frozen model config is maintained.

Also applies to: 281-284

web/src/views/CompanyPage.vue (1)

384-384: LGTM!

Passing agent names derived from the reactive agents computed property correctly supplies the dropdown options to the department form dialog. The fallback handling (agentNames ?? []) in the child component ensures safety when agents list is empty.

src/synthorg/communication/delegation/hierarchy.py (1)

44-52: LGTM!

The truthiness guard on dept.head correctly handles optional department heads. When head is None, team leads won't have a phantom supervisor relationship established, and the short-circuit evaluation ensures the comparison team.lead != dept.head is never reached with a None value.

web/src/components/company/CompanyDepartmentCard.vue (2)

22-23: LGTM!

The conditional rendering correctly handles the optional head field with a clear amber "No head assigned" fallback message. This aligns with the backend change making Department.head optional.


26-43: LGTM!

Switching from text to outlined button style improves visibility on dark backgrounds while maintaining the existing icon, size, and severity configuration. Both buttons retain their aria-label attributes for accessibility.

web/src/components/company/CompanyDepartmentFormDialog.vue (4)

19-19: LGTM!

The optional agentNames prop with safe fallback (agentNames ?? [] on line 156) correctly handles the case when no agent list is provided.


138-140: LGTM!

Adding the required marker asterisk to the Name field label is consistent with the canSave validation logic that requires a non-empty name.


193-195: LGTM!

The helper text under each JSON accordion section provides clear guidance on the expected data structure, improving form usability.

Also applies to: 202-204, 211-213


153-161: Test PrimeVue Select editable behavior for edge cases.

The editable prop correctly allows users to type custom agent names beyond the dropdown list. Verify these scenarios work correctly:

  1. User types a name, clears the input, then selects from the dropdown
  2. User edits an existing department where the current head is not present in the agentNames list

Ensure the model binding handles these cases without losing or corrupting the selected value.

web/src/components/settings/SettingSourceBadge.vue (1)

10-12: Label mapping update is correct and type-safe.

db → "Custom" and yaml → "Config File" cleanly implement the UX rename without widening source types.

web/src/__tests__/components/settings/SettingSourceBadge.test.ts (1)

15-17: Test expectations were updated correctly for renamed badges.

This keeps assertions aligned with the new db/yaml display labels.

src/synthorg/settings/definitions/api.py (1)

171-171: setup_complete advanced-level classification is correctly applied.

This aligns backend metadata with UI filtering behavior for internal setup state.

web/src/__tests__/components/settings/SettingGroupRenderer.test.ts (1)

216-232: Hidden-setting regression test is well-targeted.

This directly protects the api/setup_complete filtering path when advanced mode is enabled.

web/src/__tests__/components/settings/SettingField.test.ts (2)

131-131: Source-label expectation update is correct.

The test now matches the renamed "Config File" badge text.


395-415: Good coverage for environment-sourced read-only UX.

These cases validate both disabled input behavior and conditional env messaging.

web/src/components/company/CompanyGeneralForm.vue (1)

3-3: Renderer refactor is a solid improvement.

Using SettingGroupRenderer here centralizes filtering/layout behavior and keeps event forwarding consistent.

Also applies to: 32-42

web/src/components/settings/SettingGroupRenderer.vue (3)

4-4: Good central import for hidden-setting policy.

Pulling HIDDEN_SETTINGS from shared constants keeps this behavior consistent across components.


19-22: isHidden helper is clean and correctly scoped.

Using the fully-qualified namespace/key string here is a solid way to avoid key collisions.


24-33: Hidden-entry filtering is applied consistently to both sections.

Excluding hidden entries in both basicEntries and advancedEntries closes the UI leak for system-managed settings.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a variety of UX improvements and bug fixes across the settings and company management pages. Key changes include making department heads optional and improving the form for editing them, renaming source badges for clarity, hiding internal settings from the UI, and fixing a visual bug with the 'Advanced' toggle. The code quality is high, and the changes are well-implemented with corresponding test updates. I have one suggestion to improve data integrity in the department form by restricting the department head selection to existing agents.

Comment on lines +153 to 161
<Select
input-id="dept-head"
v-model="head"
:options="agentNames ?? []"
class="w-full"
placeholder="e.g. agent-cto-001"
editable
show-clear
placeholder="Select or type agent name"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using an editable Select for the department head introduces a risk of user error, such as typos, which could lead to assigning a non-existent agent. This would be silently accepted by the backend but would cause the department to not have a functional head in the organizational hierarchy. To improve data integrity, consider removing the editable prop to enforce that the department head must be an existing agent from the dropdown.

          <Select
            input-id="dept-head"
            v-model="head"
            :options="agentNames ?? []"
            class="w-full"
            show-clear
            placeholder="Select an agent"
          />

…mini

Tests: add hierarchy test for headless departments, Department head=None
model tests, Company with headless department test. Refactor subscriber
tests to use shared helper. Fix saveBtn existence assertions in frontend.

Bugs: block env-sourced settings from save via canSave guard and Format
JSON button. Add missing @dirty handler on CompanyGeneralForm. Remove
editable prop from dept head Select to prevent typos.

Docs: update Department.head to optional in organization.md, entity
diagram in index.md, and HIDDEN_SETTINGS concept in operations.md.
Add missing autonomy_level and approval_timeout to docstrings.

Code quality: remove redundant set() in hierarchy, add namespace guard
in provider subscriber, ReadonlySet for HIDDEN_SETTINGS, security
warning for auth_exclude_paths, update FakeDepartment type.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/company/CompanyDepartmentFormDialog.vue (1)

31-31: ⚠️ Potential issue | 🟡 Minor

Potential runtime error when clearing the head Select.

PrimeVue's Select with show-clear sets the model value to null when cleared, but head is initialized as an empty string and handleSave calls head.value.trim() on line 119. If head.value is null, this will throw a TypeError.

🛠️ Proposed fix
 const head = ref('')

And update the save logic to handle null:

-  if (head.value.trim()) dept.head = head.value.trim()
+  if (head.value?.trim()) dept.head = head.value.trim()

Alternatively, coerce null to empty string in the watch that resets form state:

-      head.value = props.department.head ?? ''
+      head.value = props.department?.head ?? ''

The simplest fix is using optional chaining on line 119.

Also applies to: 119-119, 153-160

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/company/CompanyDepartmentFormDialog.vue` at line 31, The
Select's show-clear can set head to null but head is initialized as an empty
string and handleSave calls head.value.trim() (function handleSave) which will
throw if head.value is null; update the form handling so head can be null-safe —
either initialize head as ref(null) or change handleSave to use optional
chaining/coercion (e.g., head.value?.trim() ?? '' ) before using the value; also
apply the same null-safe access in the reset/populate logic that touches head
(the watch/resetForm code paths around the save/reset logic) so clearing the
Select won't cause a TypeError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/settings/test_provider_subscriber.py`:
- Around line 99-103: The test test_retry_max_attempts_change_is_noop is passing
a misleading get_return_value="5" to the helper _make_subscriber even though
retry_max_attempts does not trigger a rebuild (so settings_service.get() is
never invoked); remove the get_return_value argument (or pass None) from the
_make_subscriber call in this test, or add a short comment next to the call
clarifying the return value is irrelevant for this no-op test, and keep the rest
of the test (asserting state.model_router is old_router) unchanged.

---

Outside diff comments:
In `@web/src/components/company/CompanyDepartmentFormDialog.vue`:
- Line 31: The Select's show-clear can set head to null but head is initialized
as an empty string and handleSave calls head.value.trim() (function handleSave)
which will throw if head.value is null; update the form handling so head can be
null-safe — either initialize head as ref(null) or change handleSave to use
optional chaining/coercion (e.g., head.value?.trim() ?? '' ) before using the
value; also apply the same null-safe access in the reset/populate logic that
touches head (the watch/resetForm code paths around the save/reset logic) so
clearing the Select won't cause a TypeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 302337bc-40bd-4e5c-88fc-d92bf8873c0c

📥 Commits

Reviewing files that changed from the base of the PR and between fed9aec and 6cced15.

📒 Files selected for processing (15)
  • docs/design/index.md
  • docs/design/operations.md
  • docs/design/organization.md
  • src/synthorg/communication/delegation/hierarchy.py
  • src/synthorg/core/company.py
  • src/synthorg/settings/subscribers/provider_subscriber.py
  • tests/unit/communication/delegation/test_hierarchy.py
  • tests/unit/core/test_company.py
  • tests/unit/settings/conftest.py
  • tests/unit/settings/test_provider_subscriber.py
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/components/company/CompanyDepartmentFormDialog.vue
  • web/src/components/settings/SettingField.vue
  • web/src/utils/constants.ts
  • web/src/views/CompanyPage.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python code -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for exception handling on Python 3.14 -- enforced by ruff
Line length must be 88 characters -- enforced by ruff

Files:

  • tests/unit/core/test_company.py
  • src/synthorg/settings/subscribers/provider_subscriber.py
  • tests/unit/settings/conftest.py
  • src/synthorg/communication/delegation/hierarchy.py
  • tests/unit/communication/delegation/test_hierarchy.py
  • tests/unit/settings/test_provider_subscriber.py
  • src/synthorg/core/company.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers for tests
Use Hypothesis for property-based testing in Python with @given + @settings decorators. HYPOTHESIS_PROFILE env var controls examples (ci=50, dev=1000)
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening margins. Use asyncio.Event().wait() for indefinite blocking instead of asyncio.sleep(large_number)
Prefer @pytest.mark.parametrize for testing similar cases instead of writing multiple test functions

Files:

  • tests/unit/core/test_company.py
  • tests/unit/settings/conftest.py
  • tests/unit/communication/delegation/test_hierarchy.py
  • tests/unit/settings/test_provider_subscriber.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints and Google-style docstrings -- enforced by ruff D rules and mypy strict mode
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic collections, use copy.deepcopy() at construction and MappingProxyType for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves
Use @computed_field instead of storing redundant fields; use NotBlankStr for all identifier/name fields instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code instead of bare create_task
Functions must be < 50 lines, files < 800 lines
All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO
Every module with business logic MUST import logger via from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging or print() in application code
Use event name constants from synthorg.observability.events.<domain> modules instead of string literals (e.g., API_REQUEST_STARTED from events.api)
Always use logger.info(EVENT, key=value) for structured logging -- never use logger.info('msg %s', val) format strings
Validate input at system boundaries (user input, external APIs, config files) rather than throughout application code
Handle errors explicitly and never silently swallow them
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples -- use generic names like example-provider, example-large-001, test-provider, test-small-001
Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for Pydantic models with dict/list fields

Files:

  • src/synthorg/settings/subscribers/provider_subscriber.py
  • src/synthorg/communication/delegation/hierarchy.py
  • src/synthorg/core/company.py
web/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Vue 3 components must use TypeScript with type safety in the dashboard

Files:

  • web/src/views/CompanyPage.vue
  • web/src/utils/constants.ts
  • web/src/components/company/CompanyDepartmentFormDialog.vue
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/components/settings/SettingField.vue
web/src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for Vue linting in the dashboard and run lint checks via npm --prefix web run lint

Files:

  • web/src/views/CompanyPage.vue
  • web/src/utils/constants.ts
  • web/src/components/company/CompanyDepartmentFormDialog.vue
  • web/src/__tests__/components/settings/SettingField.test.ts
  • web/src/components/settings/SettingField.vue
web/src/__tests__/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vitest for Vue component unit tests with fast-check for property-based testing

Files:

  • web/src/__tests__/components/settings/SettingField.test.ts
web/**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use fast-check for property-based testing in Vue tests with fc.assert + fc.property

Files:

  • web/src/__tests__/components/settings/SettingField.test.ts
src/synthorg/{tools,core}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement

Files:

  • src/synthorg/core/company.py
🧠 Learnings (14)
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • docs/design/index.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • docs/design/index.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/settings/subscribers/provider_subscriber.py
  • docs/design/operations.md
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/settings/subscribers/provider_subscriber.py
  • docs/design/operations.md
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.

Applied to files:

  • src/synthorg/settings/subscribers/provider_subscriber.py
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/views/CompanyPage.vue
  • web/src/components/company/CompanyDepartmentFormDialog.vue
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings use runtime-editable persistence with precedence: DB > env > YAML > code defaults. 8 namespaces with Fernet encryption for sensitive values.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • docs/design/operations.md
🧬 Code graph analysis (3)
tests/unit/core/test_company.py (1)
src/synthorg/core/company.py (2)
  • Department (262-338)
  • Company (420-503)
tests/unit/communication/delegation/test_hierarchy.py (2)
src/synthorg/core/company.py (2)
  • Department (262-338)
  • Team (228-259)
src/synthorg/communication/delegation/hierarchy.py (2)
  • HierarchyResolver (16-282)
  • get_supervisor (141-150)
src/synthorg/core/company.py (1)
src/synthorg/hr/full_snapshot_strategy.py (1)
  • name (65-67)
🔇 Additional comments (28)
docs/design/index.md (1)

146-146: LGTM!

The Mermaid diagram correctly reflects the optional nature of Department.head using a dashed arrow and the "(Agent, optional)" label, aligning with the model change in src/synthorg/core/company.py.

docs/design/organization.md (1)

107-109: LGTM!

The documentation accurately describes the optional head field behavior, including the default value (None) and the impact on hierarchy resolution. This aligns with the implementation in hierarchy.py and the model definition in company.py.

tests/unit/core/test_company.py (2)

138-142: LGTM!

Good test coverage for the new optional head field. The test verifies both the default None value and confirms budget_percent still defaults correctly.


372-376: LGTM!

Properly validates that Company accepts departments with head=None, ensuring the model integration works correctly for headless departments.

tests/unit/settings/conftest.py (1)

46-51: LGTM!

The FakeDepartment fixture correctly mirrors the real Department model's optional head field type (str | None). The default value "lead" preserves backward compatibility with existing tests.

web/src/views/CompanyPage.vue (3)

250-256: LGTM!

The handleDirty function properly handles dirty state tracking by conditionally calling setDirty or clearDirty on the settings store. The payload is well-typed with all necessary fields.


318-318: LGTM!

The @dirty event listener is correctly wired to handleDirty, enabling dirty state propagation from CompanyGeneralForm.


393-393: LGTM!

Passing :agent-names to the department form dialog enables the agent dropdown for Department.head selection, addressing the PR objective to replace the raw text input.

src/synthorg/communication/delegation/hierarchy.py (2)

45-53: LGTM!

The conditional logic correctly guards the team-lead-to-department-head relationship creation by checking dept.head truthiness first. This properly skips headless departments while maintaining the existing self-cycle and prior-assignment guards.


25-25: LGTM!

The docstring accurately documents the new behavior: the team-lead-to-head link is skipped when head is None.

docs/design/operations.md (1)

1142-1142: LGTM!

The documentation accurately describes the new settings UI behaviors: HIDDEN_SETTINGS filtering for system-managed settings like api/setup_complete, and read-only display with warning messages for environment-sourced settings.

tests/unit/communication/delegation/test_hierarchy.py (2)

85-96: LGTM!

Excellent test coverage for the headless department scenario. The test correctly verifies that:

  1. Team leads in headless departments have no supervisor (get_supervisor returns None)
  2. Team members still report to their team lead regardless of department head status

98-113: LGTM!

Good test for the coexistence scenario. This validates that headed and headless departments work correctly together, ensuring the conditional logic in HierarchyResolver.__init__ doesn't inadvertently affect other departments.

web/src/components/company/CompanyDepartmentFormDialog.vue (2)

192-212: LGTM!

The helper text additions for the JSON accordion sections improve discoverability and align with the PR objective to add JSON help/examples and contextual hints.


138-160: LGTM!

The required asterisk on the Name field and the agent dropdown for department head improve form clarity. The input-id prop is correctly used for PrimeVue Select.

web/src/utils/constants.ts (1)

110-119: LGTM!

The implementation correctly uses ReadonlySet<string> backed by internal const arrays, addressing the previous review feedback about preventing accidental runtime mutation.

web/src/__tests__/components/settings/SettingField.test.ts (3)

127-132: LGTM!

The assertion update from "YAML" to "Config File" correctly reflects the source badge label change in the implementation.


240-246: LGTM!

The strengthened button assertions with explicit expect(...).toBeDefined() before non-null assertion !.trigger('click') improve test reliability and provide clearer failure messages.

Also applies to: 254-259, 389-394


396-418: LGTM!

Good test coverage for the new env-sourced settings behavior:

  • Verifies input is disabled when source: 'env'
  • Verifies environment variable message is shown
  • Verifies message is not shown for non-env sources
web/src/components/settings/SettingField.vue (3)

107-120: LGTM!

The isEnvSourced and isSecuritySensitive computed properties are well-implemented. The canSave guard correctly prevents saving env-sourced settings. Although isEnvSourced is referenced in canSave before its declaration, Vue's computed properties are lazily evaluated, so this works correctly.


160-165: LGTM!

The warning messages for security-sensitive and env-sourced settings provide clear guidance to users. The amber color for env-sourced and red for security-sensitive appropriately convey the severity of each concern.


230-237: LGTM!

The enum Select placeholder now shows the definition default when available, addressing the PR objective to show the effective/default value when the resolved value is empty.

src/synthorg/core/company.py (2)

271-288: LGTM!

Making Department.head optional with default=None correctly implements the PR objective to support headless departments. The docstring accurately describes that hierarchy resolution skips the team-lead-to-head link when head is None.


341-361: LGTM!

The approval_timeout field addition with default_factory=WaitForeverConfig provides a sensible default timeout policy. The docstring is properly updated to document the new attribute.

src/synthorg/settings/subscribers/provider_subscriber.py (2)

82-91: LGTM! Correct namespace check added.

The condition now properly validates both namespace == "providers" and key == "routing_strategy" before triggering a rebuild. This prevents accidental rebuilds if another namespace uses the same key name.


119-120: The bare-comma exception syntax is correct for Python 3.14.

PEP 758 explicitly allows except A, B: syntax (without parentheses) for catching multiple exceptions in Python 3.14. Since your code does not use as name to bind the exception, the bare-comma syntax is valid and required per the coding guidelines.

tests/unit/settings/test_provider_subscriber.py (2)

40-56: Good refactor centralizing mock setup.

The get_return_value parameter and conditional mock setup reduces test boilerplate while preserving flexibility for custom mock configurations.


81-97: LGTM! Tests properly verify router swap and failure semantics.

The routing strategy change test verifies the router is replaced, and the failure test correctly asserts that the old router remains in place when UnknownStrategyError is raised.

- CompanyDepartmentFormDialog: coerce head.value via ?? '' before
  .trim() to prevent TypeError when PrimeVue Select show-clear sets
  value to null
- test_provider_subscriber: remove irrelevant get_return_value for
  advisory-only noop test (svc.get() is never called)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/company/CompanyDepartmentFormDialog.vue (1)

31-31: ⚠️ Potential issue | 🟡 Minor

Type mismatch: head ref can receive null from PrimeVue Select.

The head ref is implicitly typed as Ref<string>, but the Select with show-clear (line 160) sets the model to null when cleared. While the defensive handling at lines 119-121 correctly coerces the value, the ref declaration should reflect the actual runtime type.

🔧 Proposed fix
 // Form state
 const name = ref('')
-const head = ref('')
+const head = ref<string | null>('')
 const budgetPercent = ref<number | null>(null)

This also makes line 119's null coercion self-documenting via the type system.

As per coding guidelines: "Vue 3 components must use TypeScript with type safety in the dashboard".

Also applies to: 119-121

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/company/CompanyDepartmentFormDialog.vue` at line 31, The
ref "head" is typed as Ref<string> but PrimeVue Select (with show-clear) can set
it to null; change its declaration to allow null (e.g., make head a ref<string |
null>) and update any places that dereference it (including the existing
null-coercion logic around the priorHead handling) so the type system reflects
that head may be null; locate the symbol "head" and the null-coercion code
around the previousHead/handleSubmit logic to ensure types compile and no unsafe
assumptions remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@web/src/components/company/CompanyDepartmentFormDialog.vue`:
- Line 31: The ref "head" is typed as Ref<string> but PrimeVue Select (with
show-clear) can set it to null; change its declaration to allow null (e.g., make
head a ref<string | null>) and update any places that dereference it (including
the existing null-coercion logic around the priorHead handling) so the type
system reflects that head may be null; locate the symbol "head" and the
null-coercion code around the previousHead/handleSubmit logic to ensure types
compile and no unsafe assumptions remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e706921-811b-4877-a267-6aeb2834719a

📥 Commits

Reviewing files that changed from the base of the PR and between 6cced15 and f01de5e.

📒 Files selected for processing (2)
  • tests/unit/settings/test_provider_subscriber.py
  • web/src/components/company/CompanyDepartmentFormDialog.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
web/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Vue 3 components must use TypeScript with type safety in the dashboard

Files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
web/src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for Vue linting in the dashboard and run lint checks via npm --prefix web run lint

Files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python code -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for exception handling on Python 3.14 -- enforced by ruff
Line length must be 88 characters -- enforced by ruff

Files:

  • tests/unit/settings/test_provider_subscriber.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers for tests
Use Hypothesis for property-based testing in Python with @given + @settings decorators. HYPOTHESIS_PROFILE env var controls examples (ci=50, dev=1000)
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening margins. Use asyncio.Event().wait() for indefinite blocking instead of asyncio.sleep(large_number)
Prefer @pytest.mark.parametrize for testing similar cases instead of writing multiple test functions

Files:

  • tests/unit/settings/test_provider_subscriber.py
🧠 Learnings (1)
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
🧬 Code graph analysis (1)
tests/unit/settings/test_provider_subscriber.py (1)
src/synthorg/settings/subscribers/provider_subscriber.py (2)
  • ProviderSettingsSubscriber (27-129)
  • on_settings_changed (68-91)
🔇 Additional comments (8)
tests/unit/settings/test_provider_subscriber.py (4)

40-56: LGTM!

The enhanced _make_subscriber() helper cleanly separates two use cases: default mock setup via get_return_value parameter, and custom mock injection via settings_service parameter. The conditional on line 49 correctly preserves any pre-configured behavior (like side_effect) when a custom mock is passed.


81-85: LGTM!

Clean simplification leveraging the helper's defaults. The test correctly verifies that a routing strategy change triggers a router rebuild.


99-104: LGTM!

The explanatory comment on line 100 clearly documents that advisory keys never invoke svc.get(), making the get_return_value parameter irrelevant for this test. This addresses the previous review feedback.


106-114: LGTM!

The test correctly demonstrates that when SettingsService.get() fails, the existing router is preserved. The custom mock with side_effect is properly respected by the helper's conditional logic.

web/src/components/company/CompanyDepartmentFormDialog.vue (4)

15-20: LGTM!

The new agentNames prop is properly typed as optional and safely consumed with nullish coalescing at line 158.


140-142: LGTM!

The required asterisk correctly reflects the validation logic in canSave.


152-162: LGTM!

The Select component is correctly configured with proper accessibility (input-id), safe options handling, and clear UX with show-clear and placeholder text.


194-214: LGTM!

The helper text provides useful guidance for each JSON section with consistent styling. This improves the form's usability significantly.

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 22, 2026
PrimeVue Select with show-clear sets the model value to null when
cleared.  The runtime null-coercion was already in place; this
aligns the TypeScript type so the compiler reflects reality.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/company/CompanyDepartmentFormDialog.vue`:
- Line 31: Normalize the head reactive state to use null instead of an empty
string: change the initial ref declaration of head to ref<string | null>(null)
and update any places that reset or clear head (e.g., the form reset/close logic
and any handlers that set head back to an unselected state) to assign null
rather than ''. Ensure all usages and bindings (Select components) expect string
| null so the unselected sentinel is consistently null across
CompanyDepartmentFormDialog.vue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b7de968-aa41-4ccb-af0b-779206ac15ad

📥 Commits

Reviewing files that changed from the base of the PR and between f01de5e and 4eb03b1.

📒 Files selected for processing (1)
  • web/src/components/company/CompanyDepartmentFormDialog.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Vue 3 components must use TypeScript with type safety in the dashboard

Files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
web/src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for Vue linting in the dashboard and run lint checks via npm --prefix web run lint

Files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
🧠 Learnings (1)
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
🔇 Additional comments (3)
web/src/components/company/CompanyDepartmentFormDialog.vue (3)

119-121: Good null-safe save path for optional head.

Coercing head before trim and only persisting non-empty values is a solid way to keep dept.head optional without leaking empty strings.


19-20: Good constraint of head input to selectable agent names.

Moving from free-text entry to a constrained selection reduces invalid values and better matches the optional-head workflow.

Also applies to: 152-162


140-142: UX copy improvements are clear and helpful.

The required marker and JSON panel helper text improve form discoverability and reduce ambiguity for expected payload shapes.

Also applies to: 194-196, 203-205, 212-214

Use null consistently as the unselected sentinel for the head ref,
matching budgetPercent and autonomyLevel.  PrimeVue Select with
show-clear produces null on clear, and the backend Department.head
is NotBlankStr | None -- null is the correct unselected value.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/company/CompanyDepartmentFormDialog.vue (1)

111-118: ⚠️ Potential issue | 🟡 Minor

Apply consistent null-to-undefined conversion for optional autonomy_level field.

Line 114 assigns autonomyLevel.value directly to dept.autonomy_level. While DepartmentEntry.autonomy_level accepts null, apply the same null-coalescing pattern used for budget_percent (line 113) for consistency:

-    autonomy_level: autonomyLevel.value,
+    autonomy_level: autonomyLevel.value ?? undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/company/CompanyDepartmentFormDialog.vue` around lines 111
- 118, The dept object assigns autonomy_level directly from autonomyLevel.value;
update this to mirror the budget_percent pattern by converting null to undefined
(i.e., set autonomy_level using the null-coalescing pattern like
autonomyLevel.value ?? undefined) so DepartmentEntry.autonomy_level is
consistently normalized; locate the object literal used to build dept in
CompanyDepartmentFormDialog.vue and change the autonomy_level assignment
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@web/src/components/company/CompanyDepartmentFormDialog.vue`:
- Around line 111-118: The dept object assigns autonomy_level directly from
autonomyLevel.value; update this to mirror the budget_percent pattern by
converting null to undefined (i.e., set autonomy_level using the null-coalescing
pattern like autonomyLevel.value ?? undefined) so DepartmentEntry.autonomy_level
is consistently normalized; locate the object literal used to build dept in
CompanyDepartmentFormDialog.vue and change the autonomy_level assignment
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7cba34ce-995e-4939-8aa7-c9ee635bc79e

📥 Commits

Reviewing files that changed from the base of the PR and between 4eb03b1 and 1d559c9.

📒 Files selected for processing (1)
  • web/src/components/company/CompanyDepartmentFormDialog.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Vue 3 components must use TypeScript with type safety in the dashboard

Files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
web/src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for Vue linting in the dashboard and run lint checks via npm --prefix web run lint

Files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
🧠 Learnings (1)
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/components/company/CompanyDepartmentFormDialog.vue
🔇 Additional comments (5)
web/src/components/company/CompanyDepartmentFormDialog.vue (5)

19-19: LGTM! head normalization to null is correctly implemented.

The agentNames prop is properly typed as optional, and the head state is consistently initialized and reset to null rather than empty string, aligning with PrimeVue Select's show-clear behavior.

Also applies to: 31-31, 46-46, 60-60


119-121: LGTM! Defensive handling of optional head field.

The nullish coalescing before trim correctly handles the null value from PrimeVue Select's clear action, and conditionally setting dept.head only when non-empty aligns with the backend's optional Department.head.


155-162: LGTM! Select component is properly configured.

The input-id correctly associates with the label's for attribute, show-clear pairs well with the string | null v-model type, and the agentNames ?? [] fallback gracefully handles undefined props.


194-214: LGTM! Helpful contextual hints for JSON accordion sections.

The helper text additions provide clear, concise guidance for each JSON field, improving form usability as specified in the PR objectives.


140-142: LGTM! Required field indicator added for Name.

The red asterisk clearly communicates that Name is mandatory, and the label styling is consistent with other form fields.

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 22, 2026
Use ?? undefined for autonomy_level, matching budget_percent, so
null values are omitted from the JSON payload instead of sent as
explicit nulls.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 22, 2026 12:45 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit d16a0ac into main Mar 22, 2026
28 of 29 checks passed
@Aureliolo Aureliolo deleted the feat/improve-settings branch March 22, 2026 12:45
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 22, 2026 12:45 — with GitHub Actions Inactive
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@dc6bd3f). Learn more about missing BASE report.
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #712   +/-   ##
=======================================
  Coverage        ?   92.32%           
=======================================
  Files           ?      572           
  Lines           ?    29474           
  Branches        ?     2855           
=======================================
  Hits            ?    27213           
  Misses          ?     1786           
  Partials        ?      475           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Aureliolo added a commit that referenced this pull request Mar 22, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.4.7](v0.4.6...v0.4.7)
(2026-03-22)


### Features

* add system user for CLI-to-backend authentication
([#710](#710))
([dc6bd3f](dc6bd3f))
* dev channel builds with incremental pre-releases between stable
releases ([#715](#715))
([0e8a714](0e8a714))
* replace hardcoded name pools with Faker multi-locale name generation
([#714](#714))
([5edc6ec](5edc6ec))


### Bug Fixes

* dev-release tag creation, dependabot coverage, go -C cli convention
([#730](#730))
([7634843](7634843))
* improve name generation step UX and fix sentinel expansion bug
([#739](#739))
([f03fd05](f03fd05))
* settings page UX polish -- toggle bug, source badges, form
improvements ([#712](#712))
([d16a0ac](d16a0ac))
* switch dev tags to semver and use same release pipeline as stable
([#729](#729))
([4df6b9b](4df6b9b)),
closes [#713](#713)
* unify CLI image discovery and standardize Go tooling
([#738](#738))
([712a785](712a785))
* use PAT in dev-release workflow to trigger downstream pipelines
([#716](#716))
([d767aa3](d767aa3))


### CI/CD

* bump astral-sh/setup-uv from 7.4.0 to 7.6.0 in
/.github/actions/setup-python-uv in the minor-and-patch group
([#731](#731))
([7887257](7887257))
* bump the minor-and-patch group with 3 updates
([#735](#735))
([7cd253a](7cd253a))
* bump wrangler from 4.75.0 to 4.76.0 in /.github in the minor-and-patch
group ([#732](#732))
([a6cafc7](a6cafc7))
* clean up all dev releases and tags on stable release
([#737](#737))
([8d90f5c](8d90f5c))


### Maintenance

* bump the minor-and-patch group across 2 directories with 2 updates
([#733](#733))
([2b60069](2b60069))
* bump the minor-and-patch group with 3 updates
([#734](#734))
([859bc25](859bc25))
* fix dependabot labels and add scope tags
([#736](#736))
([677eb15](677eb15))
* remove redundant pytest.mark.timeout(30) markers
([#740](#740))
([9ec2163](9ec2163))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: settings & company page UX polish (12 items)

1 participant