Skip to content

feat: Company page (department/agent management)#888

Merged
Aureliolo merged 17 commits intomainfrom
feat/company-page
Mar 28, 2026
Merged

feat: Company page (department/agent management)#888
Aureliolo merged 17 commits intomainfrom
feat/company-page

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Replace OrgEditPage placeholder with full organization editor at /org/edit
  • Three-tab interface (General, Agents, Departments) with Radix UI Tabs synced to URL params
  • CRUD for departments and agents via Drawer (edit/delete) and Dialog (create) components
  • Drag-drop reordering for departments (grid) and agents within departments (list) via @dnd-kit
  • Optimistic updates with rollback on failure for reorder operations
  • GUI/YAML editor toggle with js-yaml serialize/parse/validate
  • Company settings form: name, autonomy level, monthly budget (EUR), communication pattern
  • Zustand store extended with 9 mutation actions + 2 optimistic helpers
  • API endpoint stubs ready for backend implementation (mutation endpoints return 404 until backend is built)
  • Shared component reuse: AgentCard, DeptHealthBar, SectionCard, ConfirmDialog, Drawer, InputField, SelectField, etc.
  • Loading skeleton, empty states, error boundaries on all tabs
  • Data composition hook with polling (30s) + WebSocket real-time updates

Test plan

  • 1213 Vitest tests pass (24 new tests for this feature)
  • TypeScript type-check clean
  • ESLint clean (no errors)
  • Storybook builds successfully (6 new stories)
  • Pre-commit hooks pass
  • Visual verification at http://localhost:5173/org/edit
  • Tab switching updates URL params
  • Drag-drop reorders departments and agents
  • YAML toggle round-trips config correctly
  • Create/edit/delete dialogs open and close properly

Review coverage

Pre-reviewed by 5 agents (frontend-reviewer, api-contract-drift, issue-resolution-verifier, docs-consistency, test-quality-reviewer). 16 findings identified and addressed in second commit.

Closes #786

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Walkthrough

Replaces the Org Edit placeholder with a full Org Edit feature: OrgEditPage (URL-query tabs and YAML mode), OrgEditSkeleton, YamlEditorPanel, tab panels (GeneralTab, AgentsTab, DepartmentsTab), dialogs/drawers for creating/editing departments and agents, Storybook stories, and tests. Adds useOrgEditData hook and expands useCompanyStore with fetch and mutation actions, optimistic reorder helpers, separate saving and health error state, and WebSocket wiring. Adds API mutation endpoints and request types, YAML utilities, and js-yaml (plus typings) in web/package.json.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.58% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: Company page (department/agent management)' clearly and concisely describes the main change: implementation of a full company page with department and agent management functionality.
Description check ✅ Passed The PR description is comprehensive and relates directly to the changeset, covering the summary, implementation details, test plan, and review coverage for the company page feature.
Linked Issues check ✅ Passed The pull request successfully implements all major requirements from linked issue #786: full CRUD for departments/agents [AgentCreateDialog.tsx, DepartmentCreateDialog.tsx, AgentEditDrawer.tsx, DepartmentEditDrawer.tsx], drag-drop reordering [AgentsTab.tsx, DepartmentsTab.tsx using @dnd-kit], dual edit modes with YAML toggle [OrgEditPage.tsx, YamlEditorPanel.tsx], company settings form [GeneralTab.tsx], consistent card components [reusing existing AgentCard/DeptHealthBar], optimistic updates with rollback [store actions], unit tests [24 new tests], and unblocks #776 with UI component integration.
Out of Scope Changes check ✅ Passed All changes are in scope. Core implementation covers org editor UI, CRUD components, store extensions, API endpoints, hooks, utilities, and comprehensive tests. Documentation updates (CLAUDE.md, docs/design/) align with feature scope. Minor fix to dept-health-bar.tsx for optional health/taskCount aligns with editor requirements. All changes support the company page feature.

✏️ 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.

@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 27, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​types/​grecaptcha@​3.0.91001007180100
Addednpm/​@​astrojs/​sitemap@​3.7.21001008295100
Addednpm/​tailwindcss@​4.2.21001008498100
Addednpm/​astro@​6.1.1981008898100
Addednpm/​wrangler@​4.78.0981009296100

View full report

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA db59db4.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

License Issues

web/package.json

PackageVersionLicenseIssue Type
js-yaml^4.1.1NullUnknown License
Allowed Licenses: MIT, MIT-0, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0, PSF-2.0, Unlicense, 0BSD, CC0-1.0, CC-BY-3.0, CC-BY-4.0, Python-2.0, Python-2.0.1, LicenseRef-scancode-free-unknown, LicenseRef-scancode-protobuf, LicenseRef-scancode-google-patent-license-golang, ZPL-2.1, LGPL-2.0-only, LGPL-2.1-only, LGPL-3.0-only, LGPL-3.0-or-later, BlueOak-1.0.0, OFL-1.1
Excluded from license check: pkg:pypi/mem0ai@1.0.5, pkg:pypi/numpy@2.4.3, pkg:pypi/qdrant-client@1.17.0, pkg:pypi/posthog@7.9.12, pkg:npm/@img/sharp-wasm32@0.33.5, pkg:npm/@img/sharp-win32-ia32@0.33.5, pkg:npm/@img/sharp-win32-x64@0.33.5, pkg:golang/github.com/golangci/golangci-lint/v2@2.11.3, pkg:golang/github.com/denis-tingaikin/go-header@0.5.0, pkg:golang/github.com/ldez/structtags@0.6.1, pkg:golang/github.com/leonklingele/grouper@1.1.2, pkg:golang/github.com/xen0n/gosmopolitan@1.3.0, pkg:golang/github.com/alfatraining/structtag@1.0.0, pkg:golang/github.com/fatih/structtag@1.2.0, pkg:npm/json-schema-typed@8.0.2, pkg:npm/victory-vendor@37.3.6

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@types/js-yaml 4.0.9 🟢 6.5
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Code-Review🟢 8Found 26/30 approved changesets -- score normalized to 8
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 9license file detected
Security-Policy🟢 10security policy file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Binary-Artifacts🟢 10no binaries found in the repo
Fuzzing⚠️ 0project is not fuzzed
npm/js-yaml ^4.1.1 UnknownUnknown

Scanned Files

  • web/package-lock.json
  • web/package.json

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 implements the organization editing functionality, introducing a comprehensive suite of components, hooks, and store actions to manage company configuration. Key additions include the OrgEditPage with tabbed views for general settings, agents, and departments, a YAML editor mode for direct configuration manipulation, and optimistic UI updates for reordering entities. The feedback identifies a potential data loss issue in the YAML editor where only the company name is currently persisted, and suggests refactoring validation logic in creation dialogs to improve code clarity and resolve dependency issues in useCallback hooks.

Comment on lines +69 to +81
const handleYamlSave = useCallback(
async (parsed: Record<string, unknown>) => {
try {
await updateCompany({
company_name: parsed.company_name as string | undefined,
})
useToastStore.getState().add({ variant: 'success', title: 'Configuration saved' })
} catch {
useToastStore.getState().add({ variant: 'error', title: 'Failed to save configuration' })
}
},
[updateCompany],
)
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.

high

The YAML editor allows users to modify the entire company configuration, including agents and departments. However, the handleYamlSave function only saves a few fields from the general company settings (company_name). This is misleading and can lead to data loss from the user's perspective, as their changes to agents or departments in YAML mode won't be persisted.

I recommend updating handleYamlSave to save all fields supported by the updateCompany endpoint. For a complete fix, you might need a new API endpoint that accepts the full configuration object. Until then, it's crucial to at least save all possible fields to reduce confusion.

  const handleYamlSave = useCallback(
    async (parsed: Record<string, unknown>) => {
      try {
        await updateCompany({
          company_name: parsed.company_name as string | undefined,
          autonomy_level: parsed.autonomy_level as UpdateCompanyRequest['autonomy_level'] | undefined,
          budget_monthly: parsed.budget_monthly as number | undefined,
          communication_pattern: parsed.communication_pattern as string | undefined,
        })
        useToastStore.getState().add({ variant: 'success', title: 'Configuration saved' })
      } catch {
        useToastStore.getState().add({ variant: 'error', title: 'Failed to save configuration' })
      }
    },
    [updateCompany],
  )

Comment on lines +56 to +75
const handleSubmit = useCallback(async () => {
if (!validate()) return
setSubmitting(true)
setSubmitError(null)
try {
await onCreate({
name: form.name.trim(),
role: form.role.trim(),
department: form.department as CreateAgentOrgRequest['department'],
level: form.level,
})
setForm(INITIAL_FORM)
onOpenChange(false)
} catch (err) {
setSubmitError(getErrorMessage(err))
} finally {
setSubmitting(false)
}
// eslint-disable-next-line @eslint-react/exhaustive-deps -- validate reads form
}, [form, onCreate, onOpenChange])
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

To improve clarity and make dependencies more explicit within useCallback, consider moving the validate function inside handleSubmit. This co-locates the validation logic with its usage and makes it clear that it depends on the form state of that specific render. This also resolves the need for the eslint-disable comment for exhaustive-deps.

You would then remove the validate function from lines 47-54.

  const handleSubmit = useCallback(async () => {
    const validate = (): boolean => {
      const next: Partial<Record<keyof FormState, string>> = {}
      if (!form.name.trim()) next.name = 'Name is required'
      if (!form.role.trim()) next.role = 'Role is required'
      if (!form.department) next.department = 'Department is required'
      setErrors(next)
      return Object.keys(next).length === 0
    }

    if (!validate()) return
    setSubmitting(true)
    setSubmitError(null)
    try {
      await onCreate({
        name: form.name.trim(),
        role: form.role.trim(),
        department: form.department as CreateAgentOrgRequest['department'],
        level: form.level,
      })
      setForm(INITIAL_FORM)
      onOpenChange(false)
    } catch (err) {
      setSubmitError(getErrorMessage(err))
    } finally {
      setSubmitting(false)
    }
  }, [form, onCreate, onOpenChange])

Comment on lines +57 to +75
const handleSubmit = useCallback(async () => {
if (!validate()) return
setSubmitting(true)
setSubmitError(null)
try {
await onCreate({
name: form.name.trim(),
display_name: form.display_name.trim(),
budget_percent: Number(form.budget_percent),
})
setForm(INITIAL_FORM)
onOpenChange(false)
} catch (err) {
setSubmitError(getErrorMessage(err))
} finally {
setSubmitting(false)
}
// eslint-disable-next-line @eslint-react/exhaustive-deps -- validate reads form
}, [form, existingNames, onCreate, onOpenChange])
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

To improve clarity and make dependencies more explicit within useCallback, consider moving the validate function inside handleSubmit. This co-locates the validation logic with its usage and makes it clear that it depends on the form state of that specific render. This also resolves the need for the eslint-disable comment for exhaustive-deps.

You would then remove the validate function from lines 41-55.

  const handleSubmit = useCallback(async () => {
    const validate = (): boolean => {
      const next: Partial<Record<keyof FormState, string>> = {}
      if (!form.name.trim()) {
        next.name = 'Name is required'
      } else if (existingNames.some((n) => n.toLowerCase() === form.name.trim().toLowerCase())) {
        next.name = 'Department already exists'
      }
      if (!form.display_name.trim()) next.display_name = 'Display name is required'
      const pct = Number(form.budget_percent)
      if (!Number.isFinite(pct) || pct < 0 || pct > 100) {
        next.budget_percent = 'Must be between 0 and 100'
      }
      setErrors(next)
      return Object.keys(next).length === 0
    }

    if (!validate()) return
    setSubmitting(true)
    setSubmitError(null)
    try {
      await onCreate({
        name: form.name.trim(),
        display_name: form.display_name.trim(),
        budget_percent: Number(form.budget_percent),
      })
      setForm(INITIAL_FORM)
      onOpenChange(false)
    } catch (err) {
      setSubmitError(getErrorMessage(err))
    } finally {
      setSubmitting(false)
    }
  }, [form, existingNames, onCreate, onOpenChange])

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: 18

🤖 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/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 114-117: Add a unit test in useOrgEditData.test.ts to assert that
useOrgEditData returns the wsSetupError from the underlying websocket hook:
update the test suite that currently checks wsConnected (renderHook(() =>
useOrgEditData())) to also simulate (or mock) the websocket hook returning a
non-null wsSetupError and assert
expect(result.current.wsSetupError).toBe(theMockError) so the hook's passthrough
behavior for wsSetupError is verified; target the useOrgEditData hook and the
wsSetupError property in your assertion.

In `@web/src/__tests__/pages/org-edit/AgentsTab.test.tsx`:
- Around line 43-54: Tests in AgentsTab.test.tsx are tightly coupled to the
implementation of makeCompanyConfig(), causing brittleness; update the tests
that call renderTab() to either (A) pass an explicit, locally-constructed
company config with known agent names and department distribution (e.g., create
a config object used by renderTab so expectations like 'alice', 'bob', 'carol'
and '2 agents'/'1 agent' are deterministic) or (B) if you must use
makeCompanyConfig(), add a comment documenting the exact expected output of
makeCompanyConfig() (names and department counts) above the tests; reference the
makeCompanyConfig and renderTab calls and replace implicit factory reliance with
an explicit config construction or clear documentation so the assertions using
screen.getByText remain stable.

In `@web/src/__tests__/pages/OrgEditPage.test.tsx`:
- Around line 34-37: The mock for useOrgEditData uses a computed property name
which is unnecessary; replace the object returned in the jest.mock callback so
it directly exposes the hook (i.e., return { useOrgEditData: () =>
getOrgEditData() }) instead of using the intermediate hookName variable and
computed key; update the mock in the test file that currently references
useOrgEditData and getOrgEditData to the direct object literal for clarity.

In `@web/src/__tests__/stores/company.test.ts`:
- Line 310: The test uses mockReorderAgents.mockResolvedValue(undefined as
never) as a TypeScript workaround; instead give the mock a precise type or
return type so you can call mockResolvedValue(undefined) without assertions —
e.g., type the mockReorderAgents as a jest.Mock<Promise<void>, [...params]> or
jest.MockedFunction<() => Promise<void>> (or cast it once where it's created)
and then replace mockReorderAgents.mockResolvedValue(undefined as never) with
mockReorderAgents.mockResolvedValue(undefined) to make the intent clear; ensure
references to mockReorderAgents and mockResolvedValue are updated accordingly.

In `@web/src/__tests__/utils/yaml.test.ts`:
- Around line 14-21: Add a property-based round-trip test using fast-check to
ensure any valid CompanyConfig serializes and parses correctly: import fc from
'fast-check' in web/src/__tests__/utils/yaml.test.ts, define an arb matching the
CompanyConfig shape (company_name, agents, departments), then use
fc.assert(fc.property(companyConfigArb, config => { const yaml =
serializeToYaml(config as CompanyConfig); const parsed = parseYaml(yaml);
expect(parsed.company_name).toBe(config.company_name);
expect(parsed.agents).toHaveLength(config.agents.length);
expect(parsed.departments).toHaveLength(config.departments.length); })); place
this alongside the existing test so serializeToYaml and parseYaml are validated
for arbitrary inputs.

In `@web/src/lib/utils.ts`:
- Line 40: The runtime-to-UI status mapping incorrectly maps the 'on_leave'
runtime value to 'offline'; update the mapping so 'on_leave' maps to 'idle'
instead. Locate the switch/case or lookup that contains the case 'on_leave' (in
web/src/lib/utils.ts) and change its return value from 'offline' to 'idle' so UI
displays leave-status agents as idle (adjust any related comments/tests that
assume the old mapping).

In `@web/src/pages/org-edit/AgentCreateDialog.stories.tsx`:
- Line 14: The default story for AgentCreateDialog uses an onCreate async
handler that throws, causing the Open story to always error; change the default
exported story's onCreate to a resolved async stub (e.g., async () =>
Promise.resolve() or an async no-op) so normal interaction doesn't hit an error
path, and move the throwing behavior into a separate "Error" or "Failing" story
that supplies the rejecting onCreate to demonstrate error handling; update
references in AgentCreateDialog.stories (the Open story and the new error story)
accordingly.

In `@web/src/pages/org-edit/AgentEditDrawer.stories.tsx`:
- Around line 32-44: The story meta for AgentEditDrawer is missing Storybook
accessibility test configuration; update the meta object (symbol: meta) for
component AgentEditDrawer to include a parameters.a11y.test entry that enables
WCAG checks (e.g., setting the test config to runaxe or default axe rules) so
Storybook 10 enforces accessibility testing for this story; add the
parameters.a11y.test field alongside args in the meta object and ensure it
follows the Storybook a11y plugin shape.

In `@web/src/pages/org-edit/AgentEditDrawer.tsx`:
- Around line 45-54: The effect currently calls multiple setters (setName,
setRole, setDepartment, setLevel, setStatus, setSubmitError) causing extra
re-renders and lint warnings; replace these with a single consolidated form
state (e.g., formState and setFormState) and in the useEffect update formState
once using setFormState({ name: agent.name, role: agent.role, department:
agent.department, level: agent.level, status: agent.status, submitError: null })
when agent changes; update all places that read or set individual fields to use
formState.field and helper updater functions (or an updater that merges partial
state) so you no longer call multiple setters synchronously in the effect.

In `@web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx`:
- Line 11: The default story's onCreate handler in
DepartmentCreateDialog.stories.tsx currently throws an Error which causes the
"Open" story to fail; change the onCreate default to a non-throwing handler
(e.g., an async no-op or a resolved Promise) so normal submit interactions
succeed, and move any error simulation into a separate error-focused story
(e.g., create a DepartmentCreateDialogError story that provides an onCreate
which rejects or throws) while keeping the symbol name onCreate consistent
across stories.

In `@web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx`:
- Around line 23-35: The story meta for DepartmentEditDrawer is missing
Storybook a11y testing configuration; update the exported meta object (the const
meta for DepartmentEditDrawer) to include a parameters field with an a11y.test
entry (e.g., parameters: { a11y: { test: true } } or the project-standard
config) so the story runs WCAG checks during Storybook builds; keep existing
args and the satisfies Meta<typeof DepartmentEditDrawer> intact and add the
parameters alongside title/component/args.

In `@web/src/pages/org-edit/GeneralTab.stories.tsx`:
- Around line 13-21: The story meta for GeneralTab is missing Storybook
accessibility test configuration; update the meta object (the constant named
meta used with GeneralTab and mockConfig) to include a parameters key with an
a11y.test entry (e.g., parameters: { a11y: { test: true } } or appropriate test
config) so Storybook 10 runs WCAG checks for this story; ensure the new
parameters are added alongside the existing title/component/args in the meta
declaration.

In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 44-53: The form is initializing non-company_name fields with
hardcoded defaults; update the CompanyConfig type (the shape returned by
getCompanyConfig()) to include autonomy_level, budget_monthly, and
communication_pattern so the useEffect can set those real values instead of
defaults—modify CompanyConfig to match UpdateCompanyRequest (add autonomy_level,
budget_monthly, communication_pattern) and change the useEffect/setForm call in
GeneralTab (where setForm is invoked inside useEffect) to populate those fields
from config.autonomy_level, config.budget_monthly, and
config.communication_pattern.

In `@web/src/pages/org-edit/YamlEditorPanel.tsx`:
- Around line 17-24: The effect in YamlEditorPanel currently overwrites yamlText
whenever config changes; change the useEffect handler to first check the dirty
flag and avoid automatically calling setYamlText/setDirty/setParseError when
dirty === true (or trigger a user confirmation flow instead). Specifically,
update the useEffect that references config, serializeToYaml, setYamlText,
setDirty and setParseError so it only auto-serializes and resets state when not
dirty, and add logic to either queue the incoming config or fire a confirmation
prompt to the user when dirty is true so unsaved edits are not silently lost.

In `@web/src/pages/OrgEditPage.tsx`:
- Around line 69-81: handleYamlSave currently only extracts company_name and
calls updateCompany, so edits to other CompanyConfig fields are silently
ignored; update handleYamlSave to extract and pass all supported
UpdateCompanyRequest fields (e.g., autonomy_level, budget_monthly,
communication_pattern, agents/departments if supported) from the parsed
Record<string, unknown> into the updateCompany call, validate/convert types as
needed before awaiting updateCompany, and keep the success/error toasts via
useToastStore; ensure references to CompanyConfig/UpdateCompanyRequest match
backend expectations and fallback gracefully if fields are missing.

In `@web/src/stores/company.ts`:
- Around line 267-283: optimisticReorderAgents currently moves reordered
department agents to the end by building otherAgents and appending reordered;
update the logic to preserve original array positions by iterating over
prev.agents and, for each element whose id is in idSet and department ===
deptName, take the next item from the reordered sequence instead of removing and
appending them; use the same return undo closure (return () => set({ config:
prev })) and keep references to prev, idSet, reordered, and otherAgents (or a
pointer/index into reordered) so the resulting agents array maintains original
ordering except where items are replaced by the reordered sequence.
- Around line 243-252: The reorderAgents action currently ignores the API
response; capture the returned Department from apiReorderAgents (like
reorderDepartments does) and update the local config/state with that returned
value instead of only clearing the saving flag. Specifically, change
reorderAgents to await the result of apiReorderAgents(deptName, { agent_ids:
orderedIds }), merge or replace the relevant department/config in the store via
set(...) using that response, preserve the existing saving/saveError handling
around set({ saving: true }) and the catch that calls getErrorMessage(err), and
ensure you still re-throw the error as currently done.

In `@web/src/utils/yaml.ts`:
- Around line 32-42: The validateCompanyYaml function only checks top-level
types (company_name and that agents/departments are arrays) and intentionally
does not validate the shape of items inside those arrays; update the function's
docstring/comment above validateCompanyYaml to state that nested element
validation (e.g., AgentConfig and Department structures) is deferred to
server-side API validation so callers know arrays may contain unvalidated items
like integers or malformed objects.
🪄 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: 41faaa04-d06b-4f9c-b623-feea8fd8a131

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdcd8a and c4cc8d3.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (37)
  • CLAUDE.md
  • web/package.json
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/types.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/lib/utils.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/stores/company.ts
  • web/src/utils/yaml.ts
📜 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: Dashboard Test
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/lib/utils.ts
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/utils/yaml.ts
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/api/types.ts
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/api/endpoints/company.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/lib/utils.ts
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/utils/yaml.ts
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/api/types.ts
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/api/endpoints/company.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
web/src/**/*.stories.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
🧠 Learnings (21)
📓 Common learnings
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/).
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Applied to files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)

Applied to files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field

Applied to files:

  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
📚 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__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • web/package.json
  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • web/package.json
  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/tsconfig.json : Set 'types' explicitly (defaults to [] in TS 6, no longer auto-discovers types/*)

Applied to files:

  • web/package.json
📚 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/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to pyproject.toml : Dependencies: all versions use == in pyproject.toml. Groups: test (pytest + plugins, hypothesis), dev (includes test + ruff, mypy, pre-commit, commitizen, pip-audit). Required: mem0ai (Mem0 memory backend — the default and currently only backend). Install: uv sync installs everything (dev group is default).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T12:54:22.557Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Go 1.26+ required; CLI dependencies in `cli/go.mod` (Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to pyproject.toml : Required Python dependencies: mem0ai (Mem0 memory backend), cryptography (Fernet encryption), faker (agent name generation)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Applies to cli/go.mod : CLI uses Go 1.26+. Dependencies: Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to pyproject.toml : Python dependencies: test group (pytest + plugins, hypothesis), dev group (includes test + ruff, mypy, pre-commit, commitizen, pip-audit)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to cli/**/*.go : Go CLI (Go 1.26+) uses Cobra for commands, charmbracelet/huh for interactive CLI, charmbracelet/lipgloss for styled output. Cross-platform builds (linux/darwin/windows × amd64/arm64). GoReleaser for releases with cosign keyless signing of checksums.txt. SLSA L3 provenance attestations via actions/attest-build-provenance.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Always reuse existing components from web/src/components/ui/ before creating new ones

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentEditDrawer.tsx

[warning] 50-50:
Do not call the 'set' function 'setLevel' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 49-49:
Do not call the 'set' function 'setDepartment' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 48-48:
Do not call the 'set' function 'setRole' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 47-47:
Do not call the 'set' function 'setName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (48)
web/src/__tests__/helpers/factories.ts (1)

37-112: Solid test-factory coverage for org-edit models.

These helpers provide consistent, typed defaults and should reduce fixture duplication across org-edit tests.

CLAUDE.md (1)

328-328: Documentation update is aligned with the new YAML feature set.

The dependency line now reflects js-yaml, matching the implemented editor/utility functionality.

web/package.json (1)

32-57: Dependency additions are correctly scoped.

js-yaml + @types/js-yaml are the right minimal additions for runtime YAML support with TypeScript.

web/src/pages/org-edit/OrgEditSkeleton.stories.tsx (1)

4-12: Clean, minimal story setup for skeleton state.

Meta typing and default story export are correctly defined.

web/src/pages/org-edit/OrgEditSkeleton.tsx (1)

1-25: Well-implemented loading skeleton with solid accessibility semantics.

Good reuse of shared skeleton primitives and appropriate live-region attributes for loading state.

web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx (1)

1-63: LGTM!

The test suite provides solid coverage for AgentEditDrawer including open state rendering, form submission with onUpdate, delete button presence, confirmation dialog flow, and read-only model info display. The use of factory helpers (makeAgent, makeDepartment) keeps tests maintainable.

web/src/__tests__/pages/org-edit/GeneralTab.test.tsx (1)

1-66: LGTM!

Comprehensive test coverage for GeneralTab including null config handling, form field rendering, save payload verification, and disabled state during save operations. The tests properly verify the component's contract with the parent page.

web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx (1)

1-72: LGTM!

Solid test coverage for AgentCreateDialog including form field presence, validation error handling, correct payload structure on valid submit, error display on onCreate rejection, and conditional rendering based on open state. The expected payload at lines 48-53 aligns with the CreateAgentOrgRequest interface.

web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx (1)

1-57: LGTM!

Good test coverage for DepartmentsTab including empty state scenarios (both empty array and null config), button presence, card rendering verification, and accessible health bar queries using role="meter". The test helper renderTab with partial overrides is a clean pattern.

web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx (1)

1-76: LGTM!

Excellent test coverage for DepartmentEditDrawer. The test at lines 66-75 is particularly valuable—it verifies that budget_percent: 0 is correctly sent as 0 rather than undefined, which confirms the Number.isFinite() check in the component handles the zero edge case properly. The health bar conditional rendering tests (lines 36-44) and teams summary assertions are also well-constructed.

web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx (1)

1-67: LGTM — solid coverage for DepartmentCreateDialog.

The test suite covers the essential flows: rendering, required field validation, duplicate name detection (case-insensitive), successful creation with expected payload, and error handling on rejection. The async patterns are correctly used with waitFor and findBy queries.

One minor observation: consider adding a test for the onOpenChange callback to verify dialog close behavior (e.g., after successful creation or cancel).

web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx (1)

1-86: LGTM — comprehensive coverage of YamlEditorPanel states and behaviors.

The test suite thoroughly validates:

  • Initial render with serialized config
  • Button states (Save disabled when clean, enabled when dirty)
  • Dirty state indicator
  • Parse error handling (invalid YAML structure)
  • Validation error handling (missing required fields)
  • Successful save flow with correct payload
  • Reset functionality
  • Saving state disables buttons

The async patterns and factory usage are correct.

web/src/__tests__/pages/org-edit/AgentsTab.test.tsx (1)

1-60: Test suite covers key rendering scenarios.

Good coverage of empty states (both empty agents array and null config), presence of Add Agent button, department grouping, and agent counts.

web/src/__tests__/hooks/useOrgEditData.test.ts (1)

1-133: Good foundational test coverage for the hook.

The test suite validates initial fetch behavior, state mirroring from the store, WebSocket connection status, and that all mutation functions are exposed. The mock setup with vi.hoisted for polling and proper store reset is well-structured.

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

9-12: JSON round-trip has known data loss for special types.

The JSON round-trip intentionally strips readonly markers but also silently converts Date objects to ISO strings and drops undefined values. This is documented and acceptable for CompanyConfig which uses primitive types, but worth noting if the type ever gains Date fields.

web/src/__tests__/utils/yaml.test.ts (1)

1-73: Test coverage is good for example-based tests.

The suite covers serialization output, round-trip parsing, error cases for invalid YAML structures, and all validation branches. The explicit error message checks (e.g., toThrow('mapping'), toContain('company_name')) ensure error messages remain stable.

web/src/__tests__/stores/company.test.ts (2)

181-363: Excellent mutation and optimistic helper test coverage.

The new tests thoroughly cover:

  • CRUD operations for company, departments, and agents
  • Success and failure paths with proper state assertions
  • Optimistic reorder helpers with rollback verification
  • Edge case handling (null config returns no-op rollback)

The test structure is clean with logical grouping by operation type.


361-361: No action needed. The TypeScript configuration targets ES2025 with lib ["ES2025", "DOM"], and Node.js v24.3.0 is running, both well above the ES2023 requirement for Array.prototype.toReversed(). The code is compatible.

web/src/pages/org-edit/DepartmentCreateDialog.tsx (1)

1-147: LGTM!

The component correctly implements:

  • Case-insensitive duplicate name validation
  • Numeric bounds checking for budget_percent (0-100)
  • Proper form reset on successful creation
  • Error handling with getErrorMessage for API failures
  • Accessible dialog with proper focus management (autoFocus)

Good use of existing InputField and Button components from the UI library.

web/src/api/types.ts (1)

605-656: LGTM!

The new request interfaces are well-structured:

  • Appropriate use of optional fields for partial updates
  • Consistent naming with existing API types
  • readonly arrays in reorder requests enforce immutability
  • Correct reuse of domain types (AutonomyLevel, SeniorityLevel, DepartmentName, etc.)
web/src/pages/org-edit/AgentCreateDialog.tsx (1)

1-158: LGTM!

The component follows the same pattern as DepartmentCreateDialog with proper:

  • Form state management and validation
  • Type-safe level options derived from SENIORITY_LEVEL_VALUES
  • Error handling via getErrorMessage
  • Consistent use of shared UI components (InputField, SelectField, Button)

The type assertion on line 64 (form.department as CreateAgentOrgRequest['department']) is acceptable since SelectField constrains values to valid department names.

web/src/pages/org-edit/YamlEditorPanel.tsx (1)

55-77: LGTM on accessibility and design token usage.

Good implementation details:

  • aria-label="YAML editor" on textarea
  • role="alert" on error message for screen reader announcements
  • Semantic classes (text-danger, text-warning, border-border, bg-surface, font-mono)
  • Disabled states properly tied to dirty and saving flags
web/src/pages/org-edit/AgentEditDrawer.tsx (1)

89-174: LGTM on component structure and accessibility.

Good implementation:

  • Proper drawer/dialog composition with ConfirmDialog for destructive actions
  • StatusBadge reused per coding guidelines
  • Loading states (saving, deleting) correctly disable relevant actions
  • Semantic tokens used throughout (text-text-secondary, text-text-muted, border-border, text-danger)
web/src/pages/OrgEditPage.tsx (2)

83-85: Loading gate correctly prevents rendering until config is available.

The condition loading && !config ensures the skeleton is shown during initial load but allows the page to remain interactive during background refreshes when config is already populated.


126-184: LGTM on tabs implementation and error handling.

Good implementation:

  • URL-synced tabs via useSearchParams with clean default handling (removes tab param for "general")
  • ErrorBoundary wrapping each tab content for isolation
  • Proper prop drilling to tab components with all CRUD handlers
  • Consistent use of semantic Tailwind classes for styling
web/src/pages/org-edit/DepartmentsTab.tsx (3)

126-147: Drag-and-drop with optimistic updates correctly implements rollback pattern.

The implementation follows the standard optimistic update pattern:

  1. Compute new order with arrayMove
  2. Apply optimistic update via optimisticReorderDepartments (returns rollback function)
  3. Call onReorderDepartments API
  4. Invoke rollback() on failure

Note: Rapid successive drags could theoretically cause stale rollbacks (per store implementation capturing prev at call time), but this is an edge case in typical usage.


42-88: SortableDepartmentCard correctly wires drag attributes to the wrapper.

Good implementation:

  • useSortable hook provides attributes, listeners, setNodeRef properly applied
  • isDragging state reduces opacity for visual feedback
  • Nested <button> handles click for edit while outer div handles drag
  • Accessible aria-label on the edit button

151-174: Empty state correctly handles both config === null and empty departments array.

The condition !config || config.departments.length === 0 ensures the empty state renders in both cases, with the create dialog properly initialized with existingNames={[]}.

web/src/pages/org-edit/DepartmentEditDrawer.tsx (4)

1-19: LGTM!

The imports are appropriate and the props interface is well-typed with proper Promise return types for the async callbacks.


44-71: LGTM!

The save and delete handlers are well-implemented with proper error handling, state management, and the Number.isFinite() guard correctly prevents NaN from being submitted.


73-156: LGTM!

The JSX structure is clean with proper conditional rendering, uses semantic Tailwind classes and shared UI components per coding guidelines.


36-42: The review comment is incorrect — Department type does not have a budget_percent field.

The Department interface (lines 588–592 in web/src/api/types.ts) contains only name, display_name, and teams. There is no budget_percent property to initialize from. The current behavior of setting budgetPercent to '0' is intentional: it allows users to enter or update a budget allocation when editing a department, rather than pre-filling from a non-existent field. This matches the component's design pattern where display_name is synced from the department object, while budget_percent is user-editable.

			> Likely an incorrect or invalid review comment.
web/src/stores/company.ts (3)

1-53: LGTM!

The interface is well-structured with clear separation between state, fetch actions, mutations, and optimistic helpers. The aliased imports prevent naming collisions with store actions.


55-103: LGTM!

The initial state and fetch methods are correctly implemented with proper error handling and WebSocket event integration.


118-132: LGTM!

The CRUD mutation pattern is consistent: set saving state, call API, update local config, handle errors with rethrow. The implementation correctly handles the case where config is null.

web/src/pages/org-edit/AgentsTab.tsx (4)

41-76: LGTM!

The SortableAgentItem component correctly implements the dnd-kit sortable pattern with proper transform handling, drag state feedback, and accessibility attributes.


96-142: LGTM!

The agentsByDept memoization and drag handlers are correctly implemented. The logic properly prevents cross-department drops by checking indices, and the optimistic update with rollback pattern is sound.


144-167: LGTM!

The empty state handling is well-implemented with a clear call-to-action and defensive fallback for the departments prop.


178-228: LGTM!

The DndContext implementation with per-department SortableContexts correctly enables within-department reordering. The DragOverlay provides good visual feedback during drag operations.

web/src/api/endpoints/company.ts (3)

1-39: LGTM!

The imports are properly extended to include unwrapVoid and all necessary request/response types for the new mutation functions.


41-74: LGTM!

The mutation functions use appropriate HTTP methods and properly encode URL parameters. The unwrapVoid helper is correctly used for the delete operation.


76-102: LGTM!

The agent mutation functions follow the same consistent pattern with proper HTTP methods and URL encoding. The reorderAgents endpoint correctly nests under the department path.

web/src/hooks/useOrgEditData.ts (4)

1-42: LGTM!

The interface is comprehensive and the module-level constants for polling interval and WebSocket channels are appropriately scoped.


44-63: LGTM!

The individual Zustand selectors provide fine-grained subscriptions, and the mutation functions are stable store references.


71-82: LGTM!

The sequential fetch pattern correctly handles the dependency between company config and department health. The polling setup is properly cleaned up on unmount, and the eslint-disable comment appropriately documents the mount-only intent.


84-121: LGTM!

The WebSocket bindings are correctly memoized with stable channel configuration, and the return object provides a comprehensive interface for the org edit page.

web/src/__tests__/pages/OrgEditPage.test.tsx (2)

1-48: Test setup and mocking structure looks good.

The mock pattern correctly handles Vitest's hoisting by placing the component import after vi.mock. The defaultHookReturn object is well-typed against UseOrgEditDataReturn, and the helper factories provide realistic test data.

One minor observation: noopAsync resolves to undefined, but methods like createDepartment and createAgent are typed to return Promise<Department> and Promise<AgentConfig> respectively. This works for the current render-only tests, but if future tests need to assert on return values, consider creating typed mocks:

const mockDepartment = makeDepartment('test')
const mockCreateDepartment = vi.fn().mockResolvedValue(mockDepartment)

50-113: Test suite has good state coverage.

The tests methodically verify key UI states:

  • Loading skeleton (config: null + loading: true)
  • Error and save error banners
  • WebSocket disconnect warning with optional custom error
  • Tab triggers and default tab content
  • YAML toggle presence

The beforeEach correctly resets hookReturn to defaults and clears mocks, ensuring test isolation.

Comment on lines +34 to +37
vi.mock('@/hooks/useOrgEditData', () => {
const hookName = 'useOrgEditData'
return { [hookName]: () => getOrgEditData() }
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider simplifying the mock definition.

The computed property name pattern adds indirection without clear benefit here. A direct object literal is more readable:

✨ Simplified mock definition
-const getOrgEditData = vi.fn(() => hookReturn)
-vi.mock('@/hooks/useOrgEditData', () => {
-  const hookName = 'useOrgEditData'
-  return { [hookName]: () => getOrgEditData() }
-})
+const getOrgEditData = vi.fn(() => hookReturn)
+vi.mock('@/hooks/useOrgEditData', () => ({
+  useOrgEditData: () => getOrgEditData(),
+}))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mock('@/hooks/useOrgEditData', () => {
const hookName = 'useOrgEditData'
return { [hookName]: () => getOrgEditData() }
})
vi.mock('@/hooks/useOrgEditData', () => ({
useOrgEditData: () => getOrgEditData(),
}))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/OrgEditPage.test.tsx` around lines 34 - 37, The mock
for useOrgEditData uses a computed property name which is unnecessary; replace
the object returned in the jest.mock callback so it directly exposes the hook
(i.e., return { useOrgEditData: () => getOrgEditData() }) instead of using the
intermediate hookName variable and computed key; update the mock in the test
file that currently references useOrgEditData and getOrgEditData to the direct
object literal for clarity.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 19:53 — 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: 14

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

Inline comments:
In `@docs/design/page-structure.md`:
- Line 36: Update the WebSocket subscription documentation to remove the
contradiction by aligning the "WS channels" note with the "WebSocket Channel
Subscription Map": explicitly separate subscription entries for read-only vs
edit modes (e.g., show that the Org Chart subscribes to both `agents` and
`system` in read-only mode but only to `agents` in edit mode), and update any
lines referencing `agents` and `system` so mode distinctions are consistently
stated in both the "WS channels" paragraph and the "WebSocket Channel
Subscription Map".

In `@web/src/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 136-149: The test currently only checks that functions returned by
useOrgEditData exist (using toBeTypeOf) but doesn’t assert they are the exact
mocked store functions; update the test to assert identity for key mutation
functions (e.g., updateCompany and optimisticReorderDepartments) by comparing
result.current.updateCompany and result.current.optimisticReorderDepartments to
the corresponding mocks (e.g., mockUpdateCompany, mockOptReorderDepts) using
strict equality assertions (toBe) so the hook is actually wired to the store
mocks.
- Around line 119-134: The test mutates the mocked useWebSocket return value
inline and restores it at the end of the test, which can be skipped if the test
fails; change the cleanup to a guaranteed afterEach by moving the restoration
into an afterEach hook (or call vi.restoreAllMocks()/vi.resetAllMocks() in
afterEach) so useWebSocket (from '@/hooks/useWebSocket') is always reset after
each test that calls useOrgEditData; ensure the afterEach restores the default
mock shape (connected: true, reconnectExhausted: false, setupError: null) or
simply resets all mocks to their original state.

In `@web/src/api/types.ts`:
- Around line 595-603: DepartmentEditDrawer accesses department.display_name
directly in the drawer title which can render "Edit: undefined" now that
display_name is optional; update the title expression in DepartmentEditDrawer
(and any similar spots) to use a safe fallback such as `department.display_name
?? department.name` (or `department.display_name ?? 'Edit Department'` when
department may be null) so the UI shows a sensible name when display_name is
omitted.

In `@web/src/pages/org-edit/AgentCreateDialog.stories.tsx`:
- Line 17: The test story uses an unsafe cast on the onCreate stub; replace the
bogus "as never" return with a minimal valid AgentConfig object to satisfy the
onCreate contract. Update the onCreate: async () => ({}) as never to return a
small mock AgentConfig (e.g., required fields such as id/name/type or whatever
AgentConfig requires) so the signature matches; change only the returned object
in the AgentCreateDialog.stories.tsx story where onCreate is defined,
referencing the onCreate handler and the AgentConfig type.

In `@web/src/pages/org-edit/AgentEditDrawer.tsx`:
- Around line 99-101: The render creates a new Date from agent.hiring_date on
every render which the linter flags; inside the AgentEditDrawer component,
memoize the computed formatted string (e.g. const hiredDate = useMemo(() => new
Date(agent.hiring_date).toLocaleDateString(), [agent.hiring_date])) and then use
that variable in the JSX instead of calling new Date(...) inline next to
StatusBadge/toRuntimeStatus so the value is pure and stable across renders.

In `@web/src/pages/org-edit/AgentsTab.stories.tsx`:
- Around line 89-90: Replace the bogus casts in the story callbacks so they
return a real mock agent instead of using "as never": update the onCreateAgent
and onUpdateAgent handlers in AgentsTab.stories.tsx to return a proper mock
agent object (e.g., use mockAgents[0]) so their return types match the component
expectations; ensure the handlers remain async and return the mocked agent value
to satisfy typing and make the story realistic.

In `@web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx`:
- Line 14: The onCreate mock in DepartmentCreateDialog.stories.tsx currently
returns an invalid value using "as never"; update the onCreate async lambda to
construct and return a minimal valid Department object (matching the Department
type) built from the input/submitted data (e.g., id, name, optional fields)
instead of casting, so story consumers receive a real Department; locate the
onCreate mock in DepartmentCreateDialog.stories.tsx and replace the cast with a
concrete object shaped like Department derived from the function's arguments.

In `@web/src/pages/org-edit/DepartmentEditDrawer.tsx`:
- Around line 144-153: The ConfirmDialog title currently uses
department?.display_name ?? 'department' which can still be undefined if
display_name is missing; update the title expression in the ConfirmDialog props
(used with deleteOpen, setDeleteOpen, handleDelete, deleting) to provide a
stronger fallback chain such as checking department?.display_name, then
department?.name, then a literal like 'department' so the dialog title never
renders undefined; ensure the title string remains `Delete <fallback>?`.
- Around line 73-76: The Drawer title in DepartmentEditDrawer uses
department.display_name directly and can render "Edit: undefined" when
display_name is absent; update the title expression (the Drawer
open/onClose/title prop usage) to use a safe fallback (e.g., use
department.display_name ?? department.name ?? 'Department' or similar) so the
title shows a sensible string when display_name is undefined.
- Around line 36-42: In DepartmentEditDrawer's useEffect the budget is being
hardcoded to '0'; change the logic to initialize budgetPercent from the
department's existing value (e.g. setBudgetPercent(department.budget_percent !=
null ? String(department.budget_percent) : '0')) so the input shows the current
department.budget_percent when editing; update the setBudgetPercent call inside
the useEffect that references department to use department.budget_percent with a
null/undefined fallback to '0'.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 206-212: The DragOverlay currently renders activeDept.display_name
which is optional and can show "undefined"; update the DragOverlay rendering
logic (where DragOverlay and activeDept are used) to provide a safe fallback
string when display_name is missing (for example fall back to activeDept.name,
activeDept.id, or a static label like "Department") so the overlay always shows
a valid human-readable string.

In `@web/src/pages/org-edit/YamlEditorPanel.tsx`:
- Around line 33-46: validateCompanyYaml currently only checks required fields;
extend it to also type-check optional fields (e.g., autonomy_level,
budget_monthly, communication_pattern) so the editor surfaces errors before API
call. Update validateCompanyYaml in web/src/utils/yaml.ts to: when a key exists
in the parsed object, validate its type (e.g., typeof autonomy_level ===
'string' and for enums check allowed values; typeof budget_monthly === 'number';
typeof communication_pattern === 'string') and return a clear validation error
string naming the invalid field; keep OrgEditPage.handleYamlSave and
YamlEditorPanel.handleSave behavior but rely on the enhanced validateCompanyYaml
so users get editor validation instead of discovering issues only at the API
layer.

In `@web/src/pages/OrgEditPage.tsx`:
- Around line 53-55: VALID_TABS is recreated on every render; hoist it to module
scope by declaring the ReadonlySet once outside the OrgEditPage component (e.g.,
top-level const VALID_TABS = new
Set<string>(['general','agents','departments'])). Update the component to use
that top-level VALID_TABS when computing rawTab and activeTab (retain rawTab and
activeTab logic as-is), ensuring the exported types (TabValue) still apply—this
removes repeated allocation while keeping behavior unchanged.
🪄 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: 63179dd0-a7bb-49a7-8abd-f10587efb72e

📥 Commits

Reviewing files that changed from the base of the PR and between c4cc8d3 and 1ff02a1.

📒 Files selected for processing (32)
  • CLAUDE.md
  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/types.ts
  • web/src/lib/utils.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org/build-org-tree.ts
  • web/src/stores/company.ts
  • web/src/utils/yaml.ts
📜 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 Backend
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/lib/utils.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/pages/org/build-org-tree.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/utils/yaml.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/api/types.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/stores/company.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/lib/utils.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/pages/org/build-org-tree.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/utils/yaml.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/api/types.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/stores/company.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
docs/design/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update the relevant docs/design/ page to reflect approved deviations from the spec

Files:

  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
web/src/**/*.stories.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Files:

  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/stores/company.test.ts
🧠 Learnings (31)
📓 Common learnings
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/).
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to docs/design/*.md : Update the relevant docs/design/ page to reflect approved deviations from the spec

Applied to files:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 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:

  • CLAUDE.md
  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • CLAUDE.md
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to pyproject.toml : Dependencies: all versions use == in pyproject.toml. Groups: test (pytest + plugins, hypothesis), dev (includes test + ruff, mypy, pre-commit, commitizen, pip-audit). Required: mem0ai (Mem0 memory backend — the default and currently only backend). Install: uv sync installs everything (dev group is default).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T12:54:22.557Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Go 1.26+ required; CLI dependencies in `cli/go.mod` (Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to pyproject.toml : Required Python dependencies: mem0ai (Mem0 memory backend), cryptography (Fernet encryption), faker (agent name generation)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Applies to cli/go.mod : CLI uses Go 1.26+. Dependencies: Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to pyproject.toml : Python dependencies: test group (pytest + plugins, hypothesis), dev group (includes test + ruff, mypy, pre-commit, commitizen, pip-audit)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to cli/**/*.go : Go CLI (Go 1.26+) uses Cobra for commands, charmbracelet/huh for interactive CLI, charmbracelet/lipgloss for styled output. Cross-platform builds (linux/darwin/windows × amd64/arm64). GoReleaser for releases with cosign keyless signing of checksums.txt. SLSA L3 provenance attestations via actions/attest-build-provenance.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/stores/company.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Applied to files:

  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)

Applied to files:

  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field

Applied to files:

  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
📚 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__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/stores/company.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/stores/company.test.ts
📚 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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/page-structure.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/page-structure.md
📚 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/page-structure.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component

Applied to files:

  • web/src/pages/org-edit/AgentEditDrawer.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/DepartmentEditDrawer.tsx

[warning] 38-38:
Do not call the 'set' function 'setDisplayName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

web/src/pages/org-edit/AgentEditDrawer.tsx

[warning] 100-100:
Do not call 'new Date(agent.hiring_date)' during render. Components and hooks must be pure. Move this call into an event handler, effect, or state initializer


[warning] 56-56:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 49-49:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (36)
web/src/pages/org/build-org-tree.ts (1)

107-107: Good fallback for optional department display names.

Using dept.display_name ?? dept.name keeps displayName always populated for downstream node rendering.

docs/design/brand-and-ux.md (1)

242-242: Utility inventory update looks correct.

Adding toRuntimeStatus() in the design-system utility list keeps the docs in sync with the status-mapping layer used by Org Edit flows.

web/src/pages/org-edit/OrgEditSkeleton.stories.tsx (1)

4-15: Story config is clean and compliant.

Good use of typed meta and parameters.a11y.test: 'error' for Storybook 10 enforcement.

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

27-33: Validation scope is now clearly documented.

The docstring explicitly states top-level-only checks and server-side nested validation, which removes ambiguity for callers.

web/src/__tests__/utils/yaml.test.ts (1)

77-106: Nice addition of property-based coverage.

The fast-check round-trip test complements the example-based cases well and strengthens regression safety.

CLAUDE.md (1)

328-328: ⚠️ Potential issue | 🟠 Major

Add explicit Node.js version constraint to web/package.json.

The Node.js 22+ and TypeScript 6.0+ claims are correct and enforced in CI across all dashboard jobs. However, web/package.json lacks an explicit "engines" field to prevent local installations on lower Node versions, creating potential friction during contributor onboarding when npm ci succeeds locally but CI fails. Add "engines": {"node": ">=22"} to align local and CI constraints.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/tsconfig.json : Remove explicit 'esModuleInterop: true' from tsconfig (always true in TS 6, causes deprecation warning)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/tsconfig.json : Set 'types' explicitly (defaults to [] in TS 6, no longer auto-discovers types/*)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/tsconfig.json : Use 'moduleResolution: bundler' or 'nodenext' (classic and node10 removed in TS 6)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.
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`).
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/).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx (1)

23-38: Looks good: Story meta is Storybook-10 compliant and well-typed.

parameters.a11y.test is configured and args cover key states cleanly.

web/src/pages/org-edit/GeneralTab.stories.tsx (1)

13-37: Good story coverage for core UI states.

Default, null-config, and saving states are all represented with correct Storybook meta configuration.

web/src/pages/org-edit/AgentEditDrawer.stories.tsx (1)

32-56: Nice addition: this story file is solid and practical.

The Open and Saving states plus realistic mock data make this component easier to validate visually and interactively.

web/src/__tests__/pages/org-edit/AgentsTab.test.tsx (1)

9-75: Great test structure and deterministic fixtures.

Using knownConfig here makes expectations explicit and keeps the suite resilient and easy to maintain.

web/src/pages/org-edit/YamlEditorPanel.stories.tsx (1)

38-58: LGTM for this story module.

The default and saving scenarios are well covered, and the metadata is correctly configured for Storybook accessibility checks.

web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx (1)

1-95: Well-structured test suite with good coverage.

The tests cover key scenarios including conditional health bar rendering, form submission with edge cases (budget_percent: 0), delete confirmation flow, and error handling. The renderDrawer helper cleanly handles optional prop overrides.

web/src/pages/org-edit/DepartmentsTab.stories.tsx (1)

41-57: LGTM — story setup follows Storybook 10 conventions.

The parameters.a11y.test: 'error' correctly enforces WCAG compliance per coding guidelines. The handler stubs appropriately satisfy the async function signatures.

web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx (1)

1-96: Comprehensive test coverage for drawer interactions.

Tests properly cover the edit form, delete confirmation flow (including dialog button selection), error handling, and read-only field display. The async patterns with waitFor are correctly applied.

web/src/pages/org-edit/AgentCreateDialog.tsx (2)

35-71: Form handling and validation are well-implemented.

The component correctly:

  • Validates required fields on submit with trimmed values
  • Resets form state after successful creation
  • Handles async errors with getErrorMessage
  • Disables buttons during submission

2-2: No action needed. The import { Dialog } from 'radix-ui' is the correct and intentional pattern for this codebase. This package is a configured dependency (^1.4.3) and is consistently used throughout the project for Dialog, Tabs, AlertDialog, and Slot imports. The approach is valid and requires no changes.

			> Likely an incorrect or invalid review comment.
web/src/pages/OrgEditPage.tsx (2)

72-91: YAML save now correctly extracts all supported company fields.

The handleYamlSave implementation properly extracts company_name, autonomy_level, budget_monthly, and communication_pattern with appropriate type guards. This addresses the earlier review feedback.


136-195: Tab structure and error boundaries are well-organized.

Each tab content is wrapped in ErrorBoundary for fault isolation, and the component wiring to CRUD handlers is complete. The URL-synced tab state provides good shareability.

web/src/pages/org-edit/GeneralTab.tsx (3)

44-53: Form initialization from config now properly reads saved values.

The useEffect correctly populates form state from config with appropriate fallbacks for optional fields (autonomy_level, budget_monthly, communication_pattern). This addresses the earlier review feedback about hardcoded defaults overwriting backend state.


31-33: Budget formatting hardcodes EUR currency.

The formatBudget function displays "EUR" regardless of locale. Per PR objectives, EUR is the intentional default, but if multi-currency support is planned, this would need refactoring.


73-117: Form layout and controls are well-structured.

Uses existing UI components consistently (SectionCard, InputField, SelectField, SliderField), provides appropriate hints, and correctly disables the save button during saving state.

web/src/pages/org-edit/YamlEditorPanel.tsx (2)

17-25: Dirty state guard correctly prevents overwriting unsaved edits.

The useEffect now checks !dirty before syncing from config, addressing the earlier review feedback. The eslint-disable comment appropriately documents why dirty is excluded from dependencies.


56-78: Editor UI is accessible and follows design token conventions.

The textarea includes aria-label, error messages use role="alert", and styling uses semantic tokens (border-border, bg-surface, text-foreground, focus:ring-accent). Button states correctly reflect dirty and saving.

web/src/pages/org-edit/DepartmentCreateDialog.tsx (1)

1-144: LGTM — well-structured dialog with proper validation and error handling.

The component correctly:

  • Validates required fields and duplicate names (case-insensitive)
  • Constrains budget_percent to 0–100
  • Resets form state on successful creation
  • Displays submit errors and disables controls while submitting
web/src/pages/org-edit/AgentsTab.tsx (2)

41-76: Well-extracted SortableAgentItem component.

Good separation of the sortable item logic into its own component, keeping the JSX in map() clean and the component under the 8-line guideline. The accessibility label and focus ring styling are properly applied.


115-142: Solid optimistic update pattern with rollback.

The rollback closure is correctly captured before the await, ensuring the original state is restored on API failure.

web/src/api/types.ts (1)

620-670: Company mutation request types are well-defined.

The request interfaces properly distinguish between required fields for creation (e.g., name, display_name in CreateDepartmentRequest) and optional fields for updates. The readonly modifier on array properties ensures immutability.

web/src/pages/org-edit/AgentEditDrawer.tsx (1)

47-58: Form state consolidation looks good.

The form state is properly consolidated into a single object, which is the recommended pattern. The lint warning about setForm in an effect is acceptable here since it's a single state update triggered by the agent prop change.

The separate setSubmitError(null) call could be merged into the form state object to eliminate the extra setter call, but this is a minor optimization.

web/src/pages/org-edit/DepartmentsTab.tsx (2)

42-88: Well-structured SortableDepartmentCard component.

The component properly uses the display_name ?? dept.name fallback pattern consistently (lines 70, 72, 74). Good extraction keeping the main render clean.


126-147: Correct optimistic update pattern with rollback.

The rollback closure is captured before the async call, ensuring the original state can be restored on failure. This mirrors the pattern in AgentsTab.

web/src/__tests__/stores/company.test.ts (3)

61-71: Good test helper for consistent state reset.

The resetStore() function properly initializes all state fields including the new healthError, saving, and saveError fields, ensuring clean test isolation.


363-405: Comprehensive optimistic helper tests with rollback verification.

The tests properly verify:

  • Immediate state mutation
  • Rollback function restores original order
  • Null-config edge case returns no-op function

403-403: No action needed. The test file uses tsconfig.app.json which targets ES2025, fully supporting Array.prototype.toReversed() (ES2023 feature).

			> Likely an incorrect or invalid review comment.
web/src/stores/company.ts (3)

247-267: reorderAgents now correctly applies the API response.

The previous review noted that this action discarded the API response. This has been addressed — the updatedDept from the API is now used to update the relevant department in config (lines 253-260).


282-304: optimisticReorderAgents now preserves array positions.

The previous review noted that reordered agents were appended to the end. This has been fixed with in-place replacement logic (lines 296-301) that maintains original array positions while applying the new order within the target department.


96-107: Good observability: WS refresh errors are now logged.

The .catch() handler logs errors to console for observability while the actual error state is managed by the individual fetch methods. This helps with debugging WS-triggered refresh failures.

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: 5

♻️ Duplicate comments (2)
web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx (1)

21-21: 🧹 Nitpick | 🔵 Trivial

Prefer deriving onCreate return data from submitted args for realistic interactions.

Returning a static stubDept ignores submitted form values, which can mask interaction regressions in Storybook. Consider returning an object built from the callback input.

Suggested tweak
-    onCreate: async () => stubDept,
+    onCreate: async (data) => ({
+      ...stubDept,
+      name: data.name,
+      display_name: data.display_name,
+      budget_percent: data.budget_percent,
+      autonomy_level: data.autonomy_level,
+    }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx` at line 21, The
story's onCreate handler currently returns a fixed stubDept, which hides
form-driven behavior; update the onCreate mock in DepartmentCreateDialog.stories
(the onCreate async callback) to construct and return an object derived from its
input args (e.g., use the submitted values parameter to build the department
shape instead of returning stubDept) so the story reflects realistic
interactions and submitted form data.
docs/design/page-structure.md (1)

249-250: ⚠️ Potential issue | 🟡 Minor

The WebSocket section is still self-contradictory.

These rows correctly split read-only vs edit mode, but Line 244 still says the client subscribes to all 7 channels after login. That documents /org/edit as both agents-only and all-channels at the same time. Please align the intro/global-subscriptions prose with the new mode-specific map.

As per coding guidelines, "Update the relevant docs/design/ page to reflect approved deviations from the spec".

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

In `@docs/design/page-structure.md` around lines 249 - 250, Update the WebSocket
intro/global-subscriptions prose so it matches the mode-specific channel map:
change the text that currently says the client subscribes to all seven channels
after login to reflect that subscriptions are mode-dependent (e.g., /org
subscribes to both `agents` and `system` in read-only view while /org/edit
subscribes only to `agents` in edit mode), and ensure the "Org Chart" rows in
the table (`Org Chart` (read-only) and `Org Chart` (edit mode)) are consistent
with the prose; specifically edit the paragraph referencing global subscriptions
and the `/org` and `/org/edit` endpoints so they no longer claim both
all-channels and agents-only simultaneously.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/page-structure.md`:
- Around line 33-35: Update the design doc to reflect that the Departments tab
is currently department-CRUD only with a read-only teams/policies summary (no
nested teams/reporting/policies editing yet); mention that
web/src/pages/org-edit/DepartmentsTab.tsx and
web/src/pages/org-edit/DepartmentEditDrawer.tsx implement only CRUD plus
read-only teams, and mark nested teams/reporting/policies editing as deferred
along with the backend stub endpoints (e.g., POST
/departments/{name}/reorder-agents) so the spec matches the shipped UI.

In `@web/src/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 34-40: The test currently mocks usePolling but never asserts its
lifecycle; update the tests in useOrgEditData.test.ts to verify that
usePolling.start (mockPollingStart) is called when the hook/component mounts and
usePolling.stop (mockPollingStop) is called on unmount. Locate the mocked
usePolling setup and add assertions around the render/unmount of the hook (or
component) to expect mockPollingStart toHaveBeenCalledOnceAfterMount and
mockPollingStop toHaveBeenCalledOnceAfterUnmount; apply the same additions for
the second test block covering lines 74-79 so both polling start/stop behaviors
are explicitly asserted.

In `@web/src/pages/org-edit/AgentEditDrawer.tsx`:
- Around line 60-64: The deptOptions array is being recreated each render;
memoize it in AgentEditDrawer by wrapping the current departments.map(...) into
a useMemo so it only recalculates when departments changes (i.e., replace the
standalone deptOptions constant with a useMemo that depends on departments).
Keep the mapping values ({ value: d.name, label: d.display_name ?? d.name }) and
ensure the identifier remains deptOptions so existing consumers still work.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 104-107: The KeyboardSensor is not wired to sortable coordinates;
update the useSensors call to pass sortableKeyboardCoordinates into the
KeyboardSensor so keyboard reordering works: import sortableKeyboardCoordinates
from `@dnd-kit/sortable` and change useSensor(KeyboardSensor) to
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) in
the sensors declaration (the useSensors / useSensor setup in
DepartmentsTab.tsx).

In `@web/src/pages/OrgEditPage.tsx`:
- Around line 73-85: The YAML save handler (handleYamlSave) currently only maps
four top-level fields into updateCompany, so edits to structural fields under
agents or departments are dropped; either restrict the YAML editor to only emit
company-level fields or modify handleYamlSave to detect and persist structural
changes by including parsed.agents and parsed.departments (typed as
CompanyConfig['agents'] / ['departments']) and calling the appropriate
persistence API (e.g., extend updateCompany to accept agents/departments or
invoke the existing org-structure save function), validating with
validateCompanyYaml before sending and preserving existing typing for
UpdateCompanyRequest where needed.

---

Duplicate comments:
In `@docs/design/page-structure.md`:
- Around line 249-250: Update the WebSocket intro/global-subscriptions prose so
it matches the mode-specific channel map: change the text that currently says
the client subscribes to all seven channels after login to reflect that
subscriptions are mode-dependent (e.g., /org subscribes to both `agents` and
`system` in read-only view while /org/edit subscribes only to `agents` in edit
mode), and ensure the "Org Chart" rows in the table (`Org Chart` (read-only) and
`Org Chart` (edit mode)) are consistent with the prose; specifically edit the
paragraph referencing global subscriptions and the `/org` and `/org/edit`
endpoints so they no longer claim both all-channels and agents-only
simultaneously.

In `@web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx`:
- Line 21: The story's onCreate handler currently returns a fixed stubDept,
which hides form-driven behavior; update the onCreate mock in
DepartmentCreateDialog.stories (the onCreate async callback) to construct and
return an object derived from its input args (e.g., use the submitted values
parameter to build the department shape instead of returning stubDept) so the
story reflects realistic interactions and submitted form data.
🪄 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: fc8c04ed-a866-4653-8c20-f291e9419186

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff02a1 and 23df138.

📒 Files selected for processing (10)
  • docs/design/page-structure.md
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/utils/yaml.ts
📜 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). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/utils/yaml.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/utils/yaml.ts
web/src/**/*.stories.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
docs/design/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update the relevant docs/design/ page to reflect approved deviations from the spec

Files:

  • docs/design/page-structure.md
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
🧠 Learnings (20)
📓 Common learnings
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/).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
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
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Applied to files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)

Applied to files:

  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field

Applied to files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
📚 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/page-structure.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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to docs/design/*.md : Update the relevant docs/design/ page to reflect approved deviations from the spec

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/page-structure.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/page-structure.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/page-structure.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/page-structure.md
📚 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/page-structure.md
📚 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:

  • docs/design/page-structure.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 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__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/DepartmentEditDrawer.tsx

[warning] 39-39:
Do not call the 'set' function 'setBudgetPercent' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 38-38:
Do not call the 'set' function 'setDisplayName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

web/src/pages/org-edit/AgentEditDrawer.tsx

[warning] 56-56:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 49-49:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (12)
web/src/pages/org-edit/AgentsTab.stories.tsx (3)

83-85: Good Storybook 10 a11y enforcement configuration.

Line 84 sets a11y.test: 'error', which is exactly what we want for accessibility regression protection in stories.

As per coding guidelines, "Use 'parameters.a11y.test: \"error\" | \"todo\" | \"off\"' in Storybook 10 to enforce WCAG compliance."


89-90: Typed async handlers are now realistic and safe.

Lines 89-90 return concrete mock agents from async handlers, improving story type safety and behavior fidelity.


102-103: Empty-state story coverage is solid.

Line 103 (config: null) is a clean way to exercise and preserve the empty-state rendering path.

web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx (1)

14-16: Storybook 10 a11y configuration is correctly applied.

Nice use of parameters.a11y.test = 'error' for enforcement in this story.

As per coding guidelines: "Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance".

web/src/__tests__/hooks/useOrgEditData.test.ts (2)

119-139: Nice cleanup for wsSetupError passthrough isolation.

The scoped afterEach reset keeps this mock mutation from leaking into other tests.


141-154: Strong store wiring assertions.

Verifying function identity (not just type/existence) is the right level for this hook contract.

web/src/pages/org-edit/AgentCreateDialog.stories.tsx (2)

40-40: Good default story behavior for onCreate.

Line 40 now uses a resolved async stub (onCreate: async () => stubAgent), so the baseline story no longer forces an error path.


27-33: No action needed — background configuration is already set globally.

The Storybook 10 background configuration is already defined globally in web/.storybook/preview.ts with parameters.backgrounds.options and initialGlobals.backgrounds.value. Individual story files do not need to redefine these settings; they are automatically applied to all stories.

web/src/pages/org-edit/AgentEditDrawer.tsx (4)

47-58: Static analysis warnings are acceptable for this initialization pattern.

The lint warnings about calling setForm and setSubmitError in the effect are overly cautious. React 18+ automatically batches state updates in effects, so there's no extra re-render. This is a standard pattern for synchronizing props to local form state when the drawer opens with a new agent.


1-25: LGTM — Clean imports and hoisted constants.

Good practice hoisting LEVEL_OPTIONS and STATUS_OPTIONS outside the component to avoid recreation on each render. Proper reuse of existing UI components from @/components/ui/.


66-95: LGTM — Well-structured async handlers with proper error handling.

Both handleSave and handleDelete follow good patterns:

  • Proper use of useCallback with complete dependency arrays
  • Error state cleared before operations
  • Errors caught and displayed via getErrorMessage
  • finally block ensures deleting state resets even on failure

97-182: LGTM — Clean JSX following design system guidelines.

The rendering follows all coding guidelines:

  • Reuses Drawer, InputField, SelectField, ConfirmDialog, Button, StatusBadge from @/components/ui/
  • Uses semantic classes (text-text-secondary, text-text-muted, text-danger, border-border)
  • Uses font-mono for model info (per font token guidelines)
  • Proper loading states for both save (saving prop) and delete (deleting state)
  • Error display with appropriate styling

Comment on lines +73 to +85
const handleYamlSave = useCallback(
async (parsed: Record<string, unknown>) => {
try {
await updateCompany({
company_name: typeof parsed.company_name === 'string' ? parsed.company_name : undefined,
autonomy_level: typeof parsed.autonomy_level === 'string'
? (parsed.autonomy_level as UpdateCompanyRequest['autonomy_level'])
: undefined,
budget_monthly: typeof parsed.budget_monthly === 'number' ? parsed.budget_monthly : undefined,
communication_pattern: typeof parsed.communication_pattern === 'string'
? parsed.communication_pattern
: undefined,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

YAML mode still drops agents and departments edits.

The editor serializes the full CompanyConfig, and validateCompanyYaml accepts agents/departments, but this save handler only forwards the four company-level fields to updateCompany. Any structural YAML edits under agents: or departments: will look savable and then be silently discarded.

Either scope the YAML editor to company-only fields, or add a save path that persists structural org changes too.

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

In `@web/src/pages/OrgEditPage.tsx` around lines 73 - 85, The YAML save handler
(handleYamlSave) currently only maps four top-level fields into updateCompany,
so edits to structural fields under agents or departments are dropped; either
restrict the YAML editor to only emit company-level fields or modify
handleYamlSave to detect and persist structural changes by including
parsed.agents and parsed.departments (typed as CompanyConfig['agents'] /
['departments']) and calling the appropriate persistence API (e.g., extend
updateCompany to accept agents/departments or invoke the existing org-structure
save function), validating with validateCompanyYaml before sending and
preserving existing typing for UpdateCompanyRequest where needed.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 20:31 — 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: 5

🤖 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/pages/org-edit/AgentEditDrawer.tsx`:
- Around line 69-84: The submit handler handleSave currently sends name:
form.name.trim() || undefined which lets an empty name become undefined; instead
validate that the trimmed name is non-empty before calling onUpdate: compute
const name = form.name?.trim() and if (!name) setSubmitError('Name is required')
(or similar) and return; otherwise call onUpdate with name (not undefined).
Update handleSave to perform this validation and early-return to prevent
unintentionally clearing the agent's name.

In `@web/src/pages/org-edit/AgentsTab.tsx`:
- Around line 144-167: The empty-state renders two identical "Add Agent"
controls; inside the AgentsTab conditional render remove the duplicate by
deleting the EmptyState's action prop (or set it to undefined) so only the
top-right Button triggers setCreateOpen(true); keep the AgentCreateDialog usage
(open={createOpen}, onOpenChange={setCreateOpen},
departments={config?.departments ?? []}, onCreate={onCreateAgent}) intact so the
dialog still opens from the remaining button.
- Around line 59-76: The drag listeners ({...attributes} and {...listeners}) on
the outer div can steal keyboard events from the inner edit button; move the
{...attributes} and {...listeners} off the container and onto a dedicated
drag-handle element (e.g., a small button or span inside the returned JSX) so
the container still uses setNodeRef but only the handle receives drag behaviors;
update the handle to include accessible props (aria-label, role/tabIndex or use
a button) so KeyboardSensor activation works on the handle, and keep the
existing onClick on the inner edit button (AgentCard wrapper) so editing
clicks/keyboard events are not intercepted.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 139-144: When the optimistic reorder is rolled back, add visible
user feedback: in the catch block where
optimisticReorderDepartments(orderedNames) returns rollback and
onReorderDepartments(orderedNames) is awaited, call rollback() and then surface
an error toast/notification (including the caught error message if available) so
the user knows the reorder failed; keep the existing rollback behavior but
augment the catch to capture the error (catch (err)) and invoke your app's
toast/notification utility with a succinct message like "Failed to reorder
departments" and optional err details.

In `@web/src/pages/org-edit/YamlEditorPanel.tsx`:
- Around line 33-45: handleSave currently clears the dirty flag immediately
after awaiting onSave, which incorrectly marks the editor saved when the caller
swallows errors; modify the handleSave function so setDirty(false) is only
called after a successful save (i.e., move setDirty(false) to execute only when
await onSave(parsed) resolves without errors), ensure catch only sets parseError
and leaves dirty true, and update the OrgEditPage.tsx onSave implementation to
rethrow after toast/error handling so callers can detect failures (refer to
handleSave, setDirty, onSave, and OrgEditPage's save handler to locate the
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: ab3e24af-c45f-43fa-be69-a128bf2b0b8d

📥 Commits

Reviewing files that changed from the base of the PR and between 23df138 and a765428.

📒 Files selected for processing (6)
  • docs/design/page-structure.md
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
📜 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 Backend
  • GitHub Check: Build Web
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
docs/design/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update the relevant docs/design/ page to reflect approved deviations from the spec

Files:

  • docs/design/page-structure.md
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
🧠 Learnings (17)
📓 Common learnings
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/).
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
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
📚 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/page-structure.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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to docs/design/*.md : Update the relevant docs/design/ page to reflect approved deviations from the spec

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Read the relevant docs/design/ page before implementing any feature or planning any issue

Applied to files:

  • docs/design/page-structure.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/page-structure.md
📚 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/page-structure.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 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__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Always reuse existing components from web/src/components/ui/ before creating new ones

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 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/pages/org-edit/AgentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentEditDrawer.tsx

[warning] 56-56:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 49-49:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (26)
web/src/__tests__/hooks/useOrgEditData.test.ts (4)

43-72: Solid test isolation setup.

resetStore() plus beforeEach creates a clean, deterministic baseline for each case and keeps the hook tests stable.


74-89: Polling lifecycle coverage is good.

Nice coverage for mount/unmount behavior: fetch call, polling start, and polling stop are all asserted.


129-149: wsSetupError passthrough test is correctly guarded.

Good use of scoped remocking with cleanup to avoid cross-test contamination.


151-164: Great identity-level wiring assertions for mutation APIs.

Verifying function identity (toBe) is the right level of strictness for this hook contract.

docs/design/page-structure.md (5)

33-33: LGTM! Documentation accurately reflects the implemented General tab.

The updated field list (name, autonomy level, monthly budget, communication pattern) aligns with the implementation, and the note about deferred nested teams/reporting/policies editing addresses the previous review concern.


35-35: LGTM! Comprehensive API endpoint documentation.

The mutation endpoint list is thorough and correctly notes the stub status of POST /departments/{name}/reorder-agents. This aligns well with the implemented API surface.


209-209: LGTM! Query parameter routing is well-documented.

The tab switching query params (?tab=general, ?tab=agents, ?tab=departments) are clearly documented with the default specified, matching the implementation.


36-36: LGTM! WebSocket documentation is now consistent across sections.

The split between read-only mode (agents, system) and edit mode (agents only) is now clearly documented at both line 36 and in the WebSocket Channel Subscription Map (lines 249-250). This successfully addresses the previous review comment about synchronization.

Also applies to: 249-250


244-244: LGTM! WebSocket subscription clarification improves readability.

The update to "Each page subscribes only to the channels it needs" provides clearer guidance about the per-page subscription model.

web/src/pages/org-edit/AgentEditDrawer.tsx (5)

1-12: LGTM!

Imports are well-organized, using existing UI components from @/components/ui/ as per coding guidelines.


36-58: Form state consolidation and effect pattern look good.

The static analysis warnings about calling setForm and setSubmitError in the effect are false positives for this use case. Synchronizing form state with the agent prop is a standard React pattern, and consolidating into a single setForm call (as done here) is the correct approach. The effect only runs when agent changes, which is the intended behavior.


60-67: LGTM!

Good use of useMemo for both deptOptions and hiredDate to avoid unnecessary recalculations on each render.


86-98: LGTM!

The delete handler properly manages loading state with finally to ensure deleting is reset even on error, and correctly closes both the confirmation dialog and drawer on success.


100-172: LGTM!

The JSX structure correctly uses semantic Tailwind classes (text-text-secondary, text-text-muted, text-danger, border-border) and reuses existing UI components (Drawer, InputField, SelectField, Button, StatusBadge). The loading state on Save is properly indicated with spinner and disabled state.

web/src/pages/org-edit/AgentsTab.tsx (5)

1-29: LGTM!

Imports are properly organized with @dnd-kit packages, React hooks, and internal components. Good use of existing UI components from the design system.


91-94: LGTM!

Sensors are correctly configured with sortableKeyboardCoordinates for proper keyboard navigation during drag operations, as noted in the commit messages.


97-109: LGTM!

The agentsByDept grouping correctly initializes empty arrays for all departments first, then distributes agents. This ensures departments with no agents still appear in the UI.


115-142: LGTM!

The drag-end handler correctly implements the optimistic update pattern:

  1. Computes the reordered list using arrayMove
  2. Applies optimistic update and captures rollback function
  3. Awaits the API call
  4. Rolls back on failure

The dependency array is complete.


169-247: LGTM!

The main render correctly sets up DndContext with proper sensors and collision detection. Each department gets its own SortableContext with the correct items array. The DragOverlay provides visual feedback during drag operations. UI components follow the design system guidelines.

web/src/pages/org-edit/DepartmentsTab.tsx (5)

1-29: Imports and setup look correct.

The imports correctly include sortableKeyboardCoordinates from @dnd-kit/sortable and wire it to the KeyboardSensor for proper keyboard accessibility. All dnd-kit imports align with the library versions in use.


104-107: DnD configuration and keyboard accessibility are correctly implemented.

The sensors are properly configured with sortableKeyboardCoordinates for keyboard-accessible reordering, and the DragOverlay correctly falls back to activeDept.name when display_name is missing. The rectSortingStrategy is appropriate for the grid layout.

Also applies to: 185-213


151-174: Empty state and create dialog integration look good.

The empty state renders appropriately when config is null or has no departments, and the DepartmentCreateDialog correctly receives existingNames for validation purposes.

Also applies to: 215-220


79-83: Remove this comment — text-text-secondary is a valid design token.

The class is properly defined in the design system: design-tokens.css defines --so-text-secondary, and global.css exposes it as --color-text-secondary in Tailwind's theme, which generates the text-text-secondary utility class. This token is used consistently throughout the codebase.


42-88: The nested button-in-drag-wrapper pattern is intentional and doesn't cause keyboard conflicts.

The button's onClick handlers (triggered by Enter/Space) operate independently from dnd-kit's KeyboardSensor, which uses arrow keys. This same pattern is used in AgentsTab.tsx without issues, and dnd-kit's configuration here with sortableKeyboardCoordinates is correct. No changes needed.

web/src/pages/org-edit/YamlEditorPanel.tsx (2)

17-25: Good guard against dirty-overwrite on incoming config.

This correctly avoids clobbering in-progress YAML edits when external config updates arrive.


58-64: No action needed. While a shared form component (InputField) exists that handles textareas, it's designed for labeled form fields with error/hint structure. The YamlEditorPanel's textarea is a specialized code editor with custom error handling and styling (font-mono, p-4, min-h-96) that doesn't fit InputField's wrapper pattern. The current implementation correctly uses design tokens (semantic classes, no hardcoded values).

			> Likely an incorrect or invalid review comment.

Comment on lines +59 to +76
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<button
type="button"
className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
onClick={onClick}
aria-label={`Edit agent ${agent.name}`}
>
<AgentCard
name={agent.name}
role={agent.role}
department={agent.department}
status={toRuntimeStatus(agent.status)}
/>
</button>
</div>
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Drag listeners on parent div may interfere with button click.

The {...attributes} and {...listeners} are spread on the outer div, which includes pointer/mouse event handlers for dragging. The inner button has an onClick for editing. While the PointerSensor has an activationConstraint: { distance: 8 } that should distinguish clicks from drags, the KeyboardSensor listeners may still capture keyboard events (like Enter/Space) before they reach the button.

Consider moving the listeners to a dedicated drag handle element to avoid any interaction conflicts.

♻️ Optional: Add a dedicated drag handle
+import { GripVertical } from 'lucide-react'
 ...
 function SortableAgentItem({
   agent,
   onClick,
 }: {
   agent: AgentConfig
   onClick: () => void
 }) {
   const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
     id: agent.id,
     data: { agent },
   })

   const style = {
     transform: CSS.Transform.toString(transform),
     transition,
     opacity: isDragging ? 0.5 : 1,
   }

   return (
-    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
+    <div ref={setNodeRef} style={style} className="flex items-center gap-2">
+      <button
+        type="button"
+        className="cursor-grab touch-none text-text-muted hover:text-text-secondary"
+        aria-label="Drag to reorder"
+        {...attributes}
+        {...listeners}
+      >
+        <GripVertical className="size-4" />
+      </button>
       <button
         type="button"
-        className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
+        className="flex-1 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
         onClick={onClick}
         aria-label={`Edit agent ${agent.name}`}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/AgentsTab.tsx` around lines 59 - 76, The drag
listeners ({...attributes} and {...listeners}) on the outer div can steal
keyboard events from the inner edit button; move the {...attributes} and
{...listeners} off the container and onto a dedicated drag-handle element (e.g.,
a small button or span inside the returned JSX) so the container still uses
setNodeRef but only the handle receives drag behaviors; update the handle to
include accessible props (aria-label, role/tabIndex or use a button) so
KeyboardSensor activation works on the handle, and keep the existing onClick on
the inner edit button (AgentCard wrapper) so editing clicks/keyboard events are
not intercepted.

Comment on lines +33 to +45
const handleSave = useCallback(async () => {
try {
const parsed = parseYaml(yamlText)
const validationError = validateCompanyYaml(parsed)
if (validationError) {
setParseError(validationError)
return
}
await onSave(parsed)
setDirty(false)
} catch (err) {
setParseError(err instanceof Error ? err.message : 'Failed to parse YAML')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not clear unsaved state unless save actually fails/succeeds transparently.

setDirty(false) currently assumes onSave rejects on failure. But web/src/pages/OrgEditPage.tsx (Line 73-92) catches update errors and resolves, so this panel can show a saved state after a failed mutation.

💡 Suggested fix (panel-side + clearer error semantics)
   const handleSave = useCallback(async () => {
-    try {
-      const parsed = parseYaml(yamlText)
-      const validationError = validateCompanyYaml(parsed)
-      if (validationError) {
-        setParseError(validationError)
-        return
-      }
-      await onSave(parsed)
-      setDirty(false)
-    } catch (err) {
-      setParseError(err instanceof Error ? err.message : 'Failed to parse YAML')
-    }
+    const parsed = parseYaml(yamlText)
+    const validationError = validateCompanyYaml(parsed)
+    if (validationError) {
+      setParseError(validationError)
+      return
+    }
+
+    try {
+      await onSave(parsed) // caller must reject on mutation failure
+      setParseError(null)
+      setDirty(false)
+    } catch (err) {
+      setParseError(err instanceof Error ? err.message : 'Failed to save YAML')
+    }
   }, [yamlText, onSave])

Please also make the onSave implementation rethrow after toast/error handling so this component can keep dirty=true on failed persistence.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSave = useCallback(async () => {
try {
const parsed = parseYaml(yamlText)
const validationError = validateCompanyYaml(parsed)
if (validationError) {
setParseError(validationError)
return
}
await onSave(parsed)
setDirty(false)
} catch (err) {
setParseError(err instanceof Error ? err.message : 'Failed to parse YAML')
}
const handleSave = useCallback(async () => {
const parsed = parseYaml(yamlText)
const validationError = validateCompanyYaml(parsed)
if (validationError) {
setParseError(validationError)
return
}
try {
await onSave(parsed) // caller must reject on mutation failure
setParseError(null)
setDirty(false)
} catch (err) {
setParseError(err instanceof Error ? err.message : 'Failed to save YAML')
}
}, [yamlText, onSave])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/YamlEditorPanel.tsx` around lines 33 - 45, handleSave
currently clears the dirty flag immediately after awaiting onSave, which
incorrectly marks the editor saved when the caller swallows errors; modify the
handleSave function so setDirty(false) is only called after a successful save
(i.e., move setDirty(false) to execute only when await onSave(parsed) resolves
without errors), ensure catch only sets parseError and leaves dirty true, and
update the OrgEditPage.tsx onSave implementation to rethrow after toast/error
handling so callers can detect failures (refer to handleSave, setDirty, onSave,
and OrgEditPage's save handler to locate the changes).

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 20:40 — 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: 6

♻️ Duplicate comments (2)
web/src/pages/OrgEditPage.tsx (1)

73-85: ⚠️ Potential issue | 🟠 Major

YAML saves still silently drop structural edits.

YamlEditorPanel validates the full parsed config before calling onSave, but this handler only forwards four company-level fields to updateCompany. Any edit under agents: or departments: will therefore resolve successfully, clear the editor's dirty state, and never persist. Either persist those sections here or reject them explicitly before returning success.

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

In `@web/src/pages/OrgEditPage.tsx` around lines 73 - 85, The YAML save handler
handleYamlSave currently only forwards four company-level fields to
updateCompany, so edits under agents: or departments: are ignored; update
handleYamlSave to detect if parsed contains agents or departments and either
include those sections in the payload passed to updateCompany (ensuring
types/shape match server UpdateCompanyRequest) or explicitly reject the save by
throwing/returning an error so YamlEditorPanel does not clear dirty state;
reference handleYamlSave, updateCompany, YamlEditorPanel and the
agents/departments keys when making the change.
web/src/pages/org-edit/AgentsTab.tsx (1)

61-67: ⚠️ Potential issue | 🔴 Critical

The click workaround disables agent dragging from the card.

The sortable listeners are on the wrapper div, but the full-width edit button now stops both pointerdown and keydown. That prevents the visible card from reaching the dnd-kit activator, so pointer reordering is effectively broken and keyboard drag is only reachable on the hidden wrapper focus target.

Suggested fix
-import { Plus, Users } from 'lucide-react'
+import { GripVertical, Plus, Users } from 'lucide-react'
...
-  return (
-    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
+  return (
+    <div ref={setNodeRef} style={style} className="flex items-start gap-2">
+      <button
+        type="button"
+        className="mt-2 rounded-md p-1 text-text-muted hover:text-foreground"
+        aria-label={`Reorder agent ${agent.name}`}
+        {...attributes}
+        {...listeners}
+      >
+        <GripVertical className="size-4" />
+      </button>
       <button
         type="button"
-        className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
+        className="flex-1 rounded-lg text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
         onClick={onClick}
-        onPointerDown={(e) => e.stopPropagation()}
-        onKeyDown={(e) => e.stopPropagation()}
         aria-label={`Edit agent ${agent.name}`}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/AgentsTab.tsx` around lines 61 - 67, The edit button's
onPointerDown and onKeyDown handlers are cancelling events needed by the
sortable wrapper (setNodeRef, attributes, listeners) and prevent drag/keyboard
reordering; remove the onPointerDown and onKeyDown stopPropagation calls from
the full-width button (leave onClick for activating edit) so pointer/keyboard
events bubble to the wrapper's listeners, or if you must conditionally block,
only stop propagation when the event should not start a drag (e.g., check a flag
or event type) — update the JSX around setNodeRef/style/attributes/listeners and
the button's handlers accordingly.
🤖 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/pages/org-edit/AgentsTab.tsx`:
- Around line 137-143: optimisticReorderAgents currently captures and restores
the entire company config which can clobber newer updates; change it so the
rollback only restores the previous agent ordering for the specific department
passed in (use the department id from the draggedAgent argument), not the whole
config. Concretely, inside optimisticReorderAgents (web/src/stores/company.ts)
capture the previous ordered list of agent ids for that department (e.g.,
prevOrder = config.departments[deptId].agents or similar), apply the optimistic
reorder, and return a rollback function that merges the current config with
prevOrder by setting only config.departments[deptId].agents back to prevOrder
(or updating the agents array for that department) rather than replacing the
whole config/state. Ensure the rollback reads the latest state and writes only
the targeted department slice so concurrent updates are preserved.
- Around line 187-218: The JSX inside Array.from(agentsByDept.entries()).map is
too large—extract it into a new DepartmentAgentsSection component that takes
props like deptName, agents, config (or dept display name) and setEditAgent;
move the SectionCard, empty-state paragraph,
SortableContext/verticalListSortingStrategy, StaggerGroup/StaggerItem mapping of
agents, and SortableAgentItem onClick handling into that component, preserving
keys (agent.id) and the action badge showing agents.length; replace the inline
map body with a simple <DepartmentAgentsSection deptName={deptName}
agents={agents} setEditAgent={setEditAgent} /> call to keep the .map concise and
testable.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 142-148: The rollback implemented by optimisticReorderDepartments
should not restore the full previous config snapshot because that can clobber
concurrent updates; change optimisticReorderDepartments so it captures only the
previous departments order (e.g., an array of department IDs/names) and returns
a rollback function that applies just that order onto the current config (merge
current config and replace only the departments ordering) instead of resetting
the whole config object; update the rollback invoked after onReorderDepartments
failure to call this new rollback (the existing call site that catches and calls
rollback() can remain) and ensure the function that writes the order uses the
store setter (the same setter used elsewhere for config) to atomically update
only the departments ordering.
- Around line 66-72: The edit button is preventing the sortable activator from
receiving pointer and keyboard events because onPointerDown and onKeyDown call
stopPropagation; remove those stopPropagation handlers (or at minimum stop
calling stopPropagation on pointerdown/keydown) so the wrapper's useSortable
(setNodeRef) can receive pointer/keyboard events for dragging, while keeping the
onClick edit handler; update the button handlers (onPointerDown/onKeyDown) to
not stop propagation and keep only onClick for edit behavior.

In `@web/src/pages/OrgEditPage.tsx`:
- Around line 95-99: The current OrgEditPage returns the editor when loading is
false but config is null, which lets AgentsTab/DepartmentsTab treat null as
empty; update the render logic in OrgEditPage to detect the failed load (loading
=== false && config === null) and return a terminal error state instead of the
editor (e.g., render an error banner or a dedicated LoadError/OrgEditError
component) so the editor and its child components (AgentsTab, DepartmentsTab)
are not mounted with an empty config; adjust any existing conditional that
currently only checks (loading && !config) and add the explicit check for failed
load to short-circuit rendering.
- Around line 104-108: The button is currently nested inside a Link
(<Link><Button ... /></Link>), which produces invalid HTML; update the pattern
to the polymorphic Button as used elsewhere: change to use Button with the
asChild prop and move Link inside it so the Link becomes the rendered child
(e.g., replace the <Link to={ROUTES.ORG}><Button ...><ArrowLeft
/></Button></Link> pattern with <Button asChild variant="ghost" size="icon"
aria-label="Back to Org Chart"><Link to={ROUTES.ORG}> <ArrowLeft
className="size-4" /> </Link></Button>), matching the approach used in
OrgChartPage.tsx and NotFoundPage.tsx.

---

Duplicate comments:
In `@web/src/pages/org-edit/AgentsTab.tsx`:
- Around line 61-67: The edit button's onPointerDown and onKeyDown handlers are
cancelling events needed by the sortable wrapper (setNodeRef, attributes,
listeners) and prevent drag/keyboard reordering; remove the onPointerDown and
onKeyDown stopPropagation calls from the full-width button (leave onClick for
activating edit) so pointer/keyboard events bubble to the wrapper's listeners,
or if you must conditionally block, only stop propagation when the event should
not start a drag (e.g., check a flag or event type) — update the JSX around
setNodeRef/style/attributes/listeners and the button's handlers accordingly.

In `@web/src/pages/OrgEditPage.tsx`:
- Around line 73-85: The YAML save handler handleYamlSave currently only
forwards four company-level fields to updateCompany, so edits under agents: or
departments: are ignored; update handleYamlSave to detect if parsed contains
agents or departments and either include those sections in the payload passed to
updateCompany (ensuring types/shape match server UpdateCompanyRequest) or
explicitly reject the save by throwing/returning an error so YamlEditorPanel
does not clear dirty state; reference handleYamlSave, updateCompany,
YamlEditorPanel and the agents/departments keys when making the change.
🪄 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: f2177a70-3f09-4084-a23e-1909e5b79cf4

📥 Commits

Reviewing files that changed from the base of the PR and between a765428 and 4e0a25f.

📒 Files selected for processing (4)
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📜 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). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
🧠 Learnings (4)
📓 Common learnings
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/).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
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
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Always reuse existing components from web/src/components/ui/ before creating new ones

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 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/pages/org-edit/AgentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentEditDrawer.tsx

[warning] 56-56:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 49-49:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (1)
web/src/pages/org-edit/AgentEditDrawer.tsx (1)

69-76: Good required-name guard.

Validating trimmedName before calling onUpdate removes the ambiguous “omit the field” path and keeps the drawer aligned with the required-name UX.

Comment on lines +66 to +72
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<button
type="button"
className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
onClick={onClick}
onPointerDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

The click workaround disables department dragging from the card.

useSortable is attached to the wrapper, but the full-card edit button now stops both pointerdown and keydown. That prevents the actual card surface from reaching the sortable activator, so pointer dragging is effectively broken and keyboard drag activation is only available on the hidden wrapper focus target.

Suggested fix
-import { Building2, Plus } from 'lucide-react'
+import { Building2, GripVertical, Plus } from 'lucide-react'
...
-  return (
-    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
+  return (
+    <div ref={setNodeRef} style={style} className="flex items-start gap-2">
+      <button
+        type="button"
+        className="mt-2 rounded-md p-1 text-text-muted hover:text-foreground"
+        aria-label={`Reorder department ${dept.display_name ?? dept.name}`}
+        {...attributes}
+        {...listeners}
+      >
+        <GripVertical className="size-4" />
+      </button>
       <button
         type="button"
-        className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
+        className="flex-1 rounded-lg text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
         onClick={onClick}
-        onPointerDown={(e) => e.stopPropagation()}
-        onKeyDown={(e) => e.stopPropagation()}
         aria-label={`Edit department ${dept.display_name ?? dept.name}`}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 66 - 72, The edit
button is preventing the sortable activator from receiving pointer and keyboard
events because onPointerDown and onKeyDown call stopPropagation; remove those
stopPropagation handlers (or at minimum stop calling stopPropagation on
pointerdown/keydown) so the wrapper's useSortable (setNodeRef) can receive
pointer/keyboard events for dragging, while keeping the onClick edit handler;
update the button handlers (onPointerDown/onKeyDown) to not stop propagation and
keep only onClick for edit behavior.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 20:56 — 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: 3

♻️ Duplicate comments (2)
web/src/pages/OrgEditPage.tsx (1)

73-85: ⚠️ Potential issue | 🟠 Major

YAML mode still silently drops structural edits.

The editor works over the full CompanyConfig, but this save path only persists the four UpdateCompanyRequest fields. Changes under agents: or departments: look accepted and are then discarded. Either scope the YAML editor to company-only fields or add a persistence path for the structural sections.

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

In `@web/src/pages/OrgEditPage.tsx` around lines 73 - 85, handleYamlSave currently
only persists four flat fields via updateCompany, so edits to structural
sections (agents, departments) in the CompanyConfig get dropped; update
handleYamlSave to either (A) limit the YAML editor input/validation to only the
four persisted company fields before calling updateCompany, or (B) persist
structural changes by extracting agents and departments from parsed and sending
them to the appropriate persistence endpoints (e.g., include them in the payload
if updateCompany supports full CompanyConfig or call the APIs that save
agents/departments). Locate and update the handleYamlSave function and the
updateCompany call to ensure parsed.agents and parsed.departments are either
validated and saved via the correct API methods or omitted by scoping the editor
input to only the company-only fields.
web/src/stores/company.ts (1)

281-289: ⚠️ Potential issue | 🟠 Major

Rollback still removes departments created during a failed reorder.

This closure rebuilds departments solely from prevOrder, so any department inserted by polling/WebSocket while the request is in flight disappears when rollback() runs. Use the same in-place replacement approach as optimisticReorderAgents so unmatched current departments stay in the array.

♻️ Suggested rollback shape
     return () => {
       const current = get().config
       if (!current) return
-      const currentMap = new Map(current.departments.map((d) => [d.name, d]))
-      const restored = prevOrder
-        .map((n) => currentMap.get(n as Department['name']))
-        .filter((d): d is Department => d !== undefined)
-      set({ config: { ...current, departments: restored } })
+      const prevSet = new Set(prevOrder)
+      const currentMap = new Map(
+        current.departments
+          .filter((d) => prevSet.has(d.name))
+          .map((d) => [d.name, d]),
+      )
+      let restoreIdx = 0
+      const restoredSequence = prevOrder
+        .map((n) => currentMap.get(n as Department['name']))
+        .filter((d): d is Department => d !== undefined)
+      const departments = current.departments.map((d) =>
+        prevSet.has(d.name) ? (restoredSequence[restoreIdx++] ?? d) : d,
+      )
+      set({ config: { ...current, departments } })
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/company.ts` around lines 281 - 289, The rollback closure
currently rebuilds config.departments from prevOrder and drops any departments
added while the request was in flight; change rollback in the same style as
optimisticReorderAgents to perform an in-place replacement that preserves
unmatched current departments: read current = get().config, build a Set or Map
from prevOrder names, then produce a new departments array by first mapping
prevOrder names to matching items from current.departments (if present) and then
appending any current.departments whose names are not in prevOrder in their
original order, and finally call set({ config: { ...current, departments:
restored } }). Reference symbols: rollback closure, get(), set(), current,
prevOrder, current.departments, optimisticReorderAgents.
🤖 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/pages/org-edit/AgentsTab.tsx`:
- Around line 219-224: DndContext is missing an onDragCancel handler so
activeAgent (the state used to render the DragOverlay) is never cleared when a
user cancels a drag; add an onDragCancel prop that calls the same cleanup as
handleDragEnd (or implement a handleDragCancel) to call setActiveAgent(null) and
any other drag-cleanup logic you run in handleDragEnd so the overlay is removed.
Target the DndContext component and the activeAgent/setActiveAgent state and
reuse handleDragEnd logic or extract shared cleanup into a helper used by both
onDragEnd and onDragCancel.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 75-80: The DeptHealthBar is being fed
health={health?.health_percent ?? 0}, which masks missing data returned by
fetchDepartmentHealths as a 0% health; change the fallback so that when health
is null/undefined you pass undefined (or omit the prop) instead of 0 (e.g.
health={health?.health_percent ?? undefined}) and then update DeptHealthBar to
explicitly handle an undefined/null health value by showing an "unavailable"
placeholder (or N/A) rather than rendering 0%. Ensure you update any
rendering/prop types in DeptHealthBar to accept health?: number | undefined and
that any percent formatting only runs when health is a number.
- Around line 188-193: The drag overlay remains visible when a drag is cancelled
because activeDept isn't cleared; in the DndContext component add an
onDragCancel prop that calls setActiveDept(null) (e.g., onDragCancel={() =>
setActiveDept(null)}) so that the overlay and activeDept are cleared on
cancellation; update the DndContext usage near the existing props (sensors,
collisionDetection, onDragStart, onDragEnd) to include onDragCancel referencing
the same setActiveDept state updater.

---

Duplicate comments:
In `@web/src/pages/OrgEditPage.tsx`:
- Around line 73-85: handleYamlSave currently only persists four flat fields via
updateCompany, so edits to structural sections (agents, departments) in the
CompanyConfig get dropped; update handleYamlSave to either (A) limit the YAML
editor input/validation to only the four persisted company fields before calling
updateCompany, or (B) persist structural changes by extracting agents and
departments from parsed and sending them to the appropriate persistence
endpoints (e.g., include them in the payload if updateCompany supports full
CompanyConfig or call the APIs that save agents/departments). Locate and update
the handleYamlSave function and the updateCompany call to ensure parsed.agents
and parsed.departments are either validated and saved via the correct API
methods or omitted by scoping the editor input to only the company-only fields.

In `@web/src/stores/company.ts`:
- Around line 281-289: The rollback closure currently rebuilds
config.departments from prevOrder and drops any departments added while the
request was in flight; change rollback in the same style as
optimisticReorderAgents to perform an in-place replacement that preserves
unmatched current departments: read current = get().config, build a Set or Map
from prevOrder names, then produce a new departments array by first mapping
prevOrder names to matching items from current.departments (if present) and then
appending any current.departments whose names are not in prevOrder in their
original order, and finally call set({ config: { ...current, departments:
restored } }). Reference symbols: rollback closure, get(), set(), current,
prevOrder, current.departments, optimisticReorderAgents.
🪄 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: d8bf569d-1c96-44bf-a255-c15aba0879c0

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0a25f and 7f07a91.

📒 Files selected for processing (4)
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
📜 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). (6)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
🧠 Learnings (6)
📓 Common learnings
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/).
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component

Applied to files:

  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not recreate status dots inline -- use <StatusBadge>

Applied to files:

  • web/src/pages/OrgEditPage.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 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/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>

Applied to files:

  • web/src/pages/org-edit/AgentsTab.tsx

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 21:12 — 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: 4

♻️ Duplicate comments (1)
web/src/pages/org-edit/DepartmentsTab.tsx (1)

66-72: ⚠️ Potential issue | 🟡 Minor

Separate the edit button from the sortable activator.

Line 71 still stops keydown on the visible card button, so Space/Enter reordering is only available from the wrapper’s extra focus stop on Line 66. Please verify by tabbing to a department card and trying to start a drag from the button itself.

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

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 66 - 72, The visible
button's onKeyDown={(e) => e.stopPropagation()} inside the department card
prevents Space/Enter from reaching the sortable activator, so remove that
stopPropagation from the button and ensure keydown handling remains on the
sortable wrapper (the div using setNodeRef, {...attributes}, {...listeners}) so
keyboard reordering works when focus is on the button; update DepartmentsTab to
either move the onKeyDown stopPropagation to the wrapper or simply delete it
from the button, keeping onClick on the button for edit and leaving
setNodeRef/attributes/listeners on the wrapper to be the sole activator for drag
keyboard events.
🤖 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/ui/dept-health-bar.tsx`:
- Around line 40-45: The DeptHealthBar div currently exposes meter semantics
even when no numeric value exists via aria-valuenow={clamped ?? 0}; change the
logic so the element only has role="meter" and the
aria-valuenow/aria-valuemin/aria-valuemax attributes when clamped (the computed
numeric value) is not null/undefined. Concretely, update the JSX around the div
to conditionally set role={clamped != null ? 'meter' : undefined} and only
include aria-valuenow, aria-valuemin and aria-valuemax when clamped != null (or
alternatively set aria-hidden when no value), and adjust the aria-label to still
indicate "unavailable" when clamped is null; refer to the DeptHealthBar
component and the clamped variable in your changes.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 75-79: The code is masking missing DepartmentHealth by passing
taskCount={health?.task_count ?? 0}; change it to propagate nullability so the
UI can show "unavailable" instead of fabricating zero: pass
taskCount={health?.task_count} (and update the DeptHealthBar prop/type if needed
to accept number | undefined or null). Ensure DeptHealthBar (and any consumers)
treat undefined/null as "data unavailable" rather than as 0.
- Line 212: The div in DepartmentsTab (the className containing "rounded-lg
border border-accent bg-card p-card shadow-lg") is using a hardcoded Tailwind
shadow; replace that raw "shadow-lg" with the design-token shadow (use the CSS
variable var(--so-shadow-card-hover) via Tailwind, e.g. the token-based shadow
utility) so the overlay shadow uses the theme token; update the className in the
DepartmentsTab component accordingly and remove "shadow-lg".

In `@web/src/stores/company.ts`:
- Around line 122-136: In createDepartment, avoid multiple sequential set()
calls: when the API succeeds, combine updating config and clearing saving into a
single set() (update get().config -> { ...prev, departments:
[...prev.departments, dept] } and saving: false in one call); similarly in the
catch block combine setting saveError and saving: false into one set() call.
Apply this same combined-set pattern to other mutation methods that currently
call set() multiple times.

---

Duplicate comments:
In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 66-72: The visible button's onKeyDown={(e) => e.stopPropagation()}
inside the department card prevents Space/Enter from reaching the sortable
activator, so remove that stopPropagation from the button and ensure keydown
handling remains on the sortable wrapper (the div using setNodeRef,
{...attributes}, {...listeners}) so keyboard reordering works when focus is on
the button; update DepartmentsTab to either move the onKeyDown stopPropagation
to the wrapper or simply delete it from the button, keeping onClick on the
button for edit and leaving setNodeRef/attributes/listeners on the wrapper to be
the sole activator for drag keyboard events.
🪄 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: 6ec81766-f0b2-4f75-b916-205ed0ee711e

📥 Commits

Reviewing files that changed from the base of the PR and between 7f07a91 and 4532ba9.

📒 Files selected for processing (4)
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
📜 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). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
web/src/components/ui/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Files:

  • web/src/components/ui/dept-health-bar.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
🧠 Learnings (5)
📓 Common learnings
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/).
📚 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/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component

Applied to files:

  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>

Applied to files:

  • web/src/pages/org-edit/AgentsTab.tsx
🔇 Additional comments (10)
web/src/stores/company.ts (4)

271-293: Targeted rollback for department reordering handles concurrent additions correctly.

The rollback logic preserves any departments added concurrently by tracking the previous order in a Set and appending new departments to the restored list. This is a good approach for maintaining data integrity during optimistic updates.


295-340: In-place agent reorder preserves array positions correctly.

The implementation addresses the prior feedback by replacing agents at their original positions rather than appending. The rollback function similarly uses targeted restoration, preserving concurrent modifications to other departments.


96-107: WebSocket refresh error handling improved with logging.

The error handler now logs failures for observability while the fetch methods handle state updates. This is a good improvement over silently swallowing errors.


247-267: Remove this comment—the data flow is correct as written.

The code properly separates concerns: optimisticReorderAgents reorders config.agents, while the API response updates config.departments with returned metadata. Since Department doesn't contain agents (agents are stored flat in CompanyConfig.agents with a department field), each update targets the appropriate state slice. The API call persists the ordering server-side, and the optimistic update ensures local consistency until the response syncs.

web/src/pages/org-edit/AgentsTab.tsx (6)

42-78: Keyboard event propagation handled to prevent drag interference.

The onKeyDown={(e) => e.stopPropagation()} on line 66 prevents keyboard events (Enter/Space) from being captured by the drag sensor, allowing the edit button to function correctly. Combined with the PointerSensor distance constraint of 8px, this should provide a good distinction between clicks and drags.

A dedicated drag handle (as suggested in prior review) would be a further UX improvement but is optional given the current implementation works correctly.


80-117: Clean extraction of per-department section component.

This addresses prior feedback about complex JSX in .map(). The component properly uses SectionCard, handles the empty state, and keeps the inner .map() concise. As per coding guidelines, this follows the "Do not create complex (>8 line) JSX inside .map()" rule.


137-150: Agent grouping logic handles orphaned departments gracefully.

The implementation initializes entries for all known departments first, then groups agents. If an agent references a department not in config.departments, the fallback ?? [] creates a new entry. This prevents runtime errors and the rendering logic uses deptName as fallback display name (line 231).


156-184: Drag end handler with proper optimistic update and rollback.

The implementation correctly:

  1. Clears the drag overlay immediately
  2. Guards against invalid states (no target, same position)
  3. Applies optimistic update before API call
  4. Rolls back and shows error toast on failure

186-208: Empty state no longer has duplicate action buttons.

The EmptyState component correctly omits the action prop, with only the top-right Button providing the "Add Agent" action. This addresses the prior feedback about redundant UI elements.


219-249: DndContext correctly handles drag cancellation.

The onDragCancel={() => setActiveAgent(null)} handler (line 224) clears the drag overlay when users cancel via Escape key, addressing prior feedback. The DragOverlay renders the active agent card with appropriate styling.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 21:25 — 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 (2)
web/src/components/ui/dept-health-bar.tsx (1)

12-27: 🧹 Nitpick | 🔵 Trivial

Align nullable prop contract with actual caller behavior.

DeptHealthBar now supports health: null, but the primary caller in web/src/pages/org-edit/DepartmentEditDrawer.tsx (Line 78-85) still short-circuits rendering with {health && (...)}. That makes this component’s unavailable-health UI path effectively dead in the main flow. Please align one side: either always render this component and pass nullable health, or keep health required here.

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

In `@web/src/components/ui/dept-health-bar.tsx` around lines 12 - 27, The
DeptHealthBar component declares health as optional/nullable but its main caller
(DepartmentEditDrawer) guards rendering with the truthy check `{health &&
<DeptHealthBar ... />}`, which prevents the component's null-health UI from ever
showing; fix by aligning the contract: either make DeptHealthBar's prop required
(remove nullable from DeptHealthBarProps and update usages to always pass a
number) or change the caller to render unconditionally and pass the nullable
value (replace `{health && <DeptHealthBar .../>}` with `<DeptHealthBar
health={health} .../>`), ensuring DeptHealthBar's internal guards (hasHealth,
clamped, getHealthColor) remain correct.
web/src/stores/company.ts (1)

65-73: ⚠️ Potential issue | 🟡 Minor

The new WS refresh .catch() still won't observe normal fetch failures.

Both fetch actions resolve after storing their error state, so updateFromWsEvent() still runs fetchDepartmentHealths() after a failed config load and the added .catch() only sees unexpected exceptions. Short-circuit on a failed company fetch, or let the fetchers rethrow when this caller needs failure semantics.

Also applies to: 75-93, 96-105

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

In `@web/src/stores/company.ts` around lines 65 - 73, fetchCompanyData currently
swallows failures by catching and storing error state, which causes
updateFromWsEvent to continue and call fetchDepartmentHealths() even after a
failed company fetch; change fetchCompanyData (and the other fetchers mentioned
around lines 75-93 and 96-105) to either rethrow the error after setting state
or short-circuit by returning a rejected Promise when getCompanyConfig (and
analogous fetch functions) fail, so callers like updateFromWsEvent can detect
failure and avoid running dependent fetches such as fetchDepartmentHealths();
reference the fetchCompanyData, getCompanyConfig, updateFromWsEvent, and
fetchDepartmentHealths symbols when applying the change.
♻️ Duplicate comments (1)
web/src/pages/org-edit/DepartmentsTab.tsx (1)

66-72: ⚠️ Potential issue | 🟠 Major

Keyboard drag is still blocked on the visible card control.

onKeyDown={(e) => e.stopPropagation()} keeps the button's key events from reaching the sortable activator, so keyboard reordering is still tied to the wrapper instead of the control users actually focus.

One clean way to separate edit vs. reorder
-import { Building2, Plus } from 'lucide-react'
+import { Building2, GripVertical, Plus } from 'lucide-react'-  return (
-    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
+  return (
+    <div ref={setNodeRef} style={style} className="flex items-start gap-2">
+      <button
+        type="button"
+        className="mt-2 rounded-md p-1 text-foreground/70 hover:text-foreground"
+        aria-label={`Reorder department ${dept.display_name ?? dept.name}`}
+        {...attributes}
+        {...listeners}
+      >
+        <GripVertical className="size-4" />
+      </button>
       <button
         type="button"
-        className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
+        className="flex-1 rounded-lg text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
         onClick={onClick}
-        onKeyDown={(e) => e.stopPropagation()}
         aria-label={`Edit department ${dept.display_name ?? dept.name}`}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 66 - 72, The button's
onKeyDown handler (onKeyDown={(e) => e.stopPropagation()}) prevents key events
from reaching the sortable activator and blocks keyboard reordering; remove that
stopPropagation from the button (the element with setNodeRef and aria-label) so
key events bubble to the sortable activator, and if you still need to stop
propagation for other keys, move a guarded onKeyDown to the outer wrapper (the
element using setNodeRef or the sortable container) and only stopPropagation for
specific keys or targets that should not trigger reorder. Ensure you update
handlers referenced here: the button's onClick/onKeyDown and the wrapper using
setNodeRef so keyboard activation for drag works from the visible control.
🤖 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/pages/org-edit/DepartmentsTab.tsx`:
- Around line 158-174: The create/reorder UX is not gated by the in-flight
saving flag; update the Add Department buttons, EmptyState action,
DepartmentCreateDialog, and the sortable card controls so they are
disabled/ignore input when saving is true. Specifically, prevent calling
setCreateOpen or onCreateDepartment while saving, pass a disabled prop (or
equivalent) into DepartmentCreateDialog and the sortable list/card components,
and ensure the EmptyState.action onClick is noop when saving; apply the same
change for the other instance referenced (the block around the sortable cards in
the 181-204 region) so no new create or reorder can start while saving is true.

---

Outside diff comments:
In `@web/src/components/ui/dept-health-bar.tsx`:
- Around line 12-27: The DeptHealthBar component declares health as
optional/nullable but its main caller (DepartmentEditDrawer) guards rendering
with the truthy check `{health && <DeptHealthBar ... />}`, which prevents the
component's null-health UI from ever showing; fix by aligning the contract:
either make DeptHealthBar's prop required (remove nullable from
DeptHealthBarProps and update usages to always pass a number) or change the
caller to render unconditionally and pass the nullable value (replace `{health
&& <DeptHealthBar .../>}` with `<DeptHealthBar health={health} .../>`), ensuring
DeptHealthBar's internal guards (hasHealth, clamped, getHealthColor) remain
correct.

In `@web/src/stores/company.ts`:
- Around line 65-73: fetchCompanyData currently swallows failures by catching
and storing error state, which causes updateFromWsEvent to continue and call
fetchDepartmentHealths() even after a failed company fetch; change
fetchCompanyData (and the other fetchers mentioned around lines 75-93 and
96-105) to either rethrow the error after setting state or short-circuit by
returning a rejected Promise when getCompanyConfig (and analogous fetch
functions) fail, so callers like updateFromWsEvent can detect failure and avoid
running dependent fetches such as fetchDepartmentHealths(); reference the
fetchCompanyData, getCompanyConfig, updateFromWsEvent, and
fetchDepartmentHealths symbols when applying the change.

---

Duplicate comments:
In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 66-72: The button's onKeyDown handler (onKeyDown={(e) =>
e.stopPropagation()}) prevents key events from reaching the sortable activator
and blocks keyboard reordering; remove that stopPropagation from the button (the
element with setNodeRef and aria-label) so key events bubble to the sortable
activator, and if you still need to stop propagation for other keys, move a
guarded onKeyDown to the outer wrapper (the element using setNodeRef or the
sortable container) and only stopPropagation for specific keys or targets that
should not trigger reorder. Ensure you update handlers referenced here: the
button's onClick/onKeyDown and the wrapper using setNodeRef so keyboard
activation for drag works from the visible control.
🪄 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: 5dfc9184-4eb5-443f-917b-4fb8eab8e732

📥 Commits

Reviewing files that changed from the base of the PR and between 4532ba9 and d34087e.

📒 Files selected for processing (3)
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
📜 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). (4)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
web/src/components/ui/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Files:

  • web/src/components/ui/dept-health-bar.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
🧠 Learnings (6)
📓 Common learnings
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/).
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
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
🔇 Additional comments (2)
web/src/components/ui/dept-health-bar.tsx (1)

41-44: Nice fix: meter semantics only when value exists.

Conditionally emitting role="meter" and aria-valuenow/min/max only when clamped is present avoids announcing unavailable health as a real numeric reading.

web/src/stores/company.ts (1)

251-318: Targeted rollback scope looks good.

Restoring only the reordered departments/agents on top of the latest config avoids clobbering unrelated polling or WebSocket updates when an optimistic reorder fails.

Comment on lines +158 to +174
<Button onClick={() => setCreateOpen(true)}>
<Plus className="mr-1.5 size-3.5" />
Add Department
</Button>
</div>
<EmptyState
icon={Building2}
title="No departments"
description="Create your first department to get started."
action={{ label: 'Add Department', onClick: () => setCreateOpen(true) }}
/>
<DepartmentCreateDialog
open={createOpen}
onOpenChange={setCreateOpen}
existingNames={[]}
onCreate={onCreateDepartment}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This tab still accepts new mutations while saving is true.

Both "Add Department" buttons and the sortable cards stay interactive during an in-flight save. A second add or reorder can start before the first request settles, so optimistic state and response application can race each other. Please gate create/reorder entry points off saving.

Also applies to: 181-204

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

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 158 - 174, The
create/reorder UX is not gated by the in-flight saving flag; update the Add
Department buttons, EmptyState action, DepartmentCreateDialog, and the sortable
card controls so they are disabled/ignore input when saving is true.
Specifically, prevent calling setCreateOpen or onCreateDepartment while saving,
pass a disabled prop (or equivalent) into DepartmentCreateDialog and the
sortable list/card components, and ensure the EmptyState.action onClick is noop
when saving; apply the same change for the other instance referenced (the block
around the sortable cards in the 181-204 region) so no new create or reorder can
start while saving is true.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 21:43 — 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

♻️ Duplicate comments (1)
web/src/pages/org-edit/DepartmentsTab.tsx (1)

194-206: ⚠️ Potential issue | 🟡 Minor

Drag reordering is not disabled during saving, risking race conditions.

While the "Add Department" buttons are disabled during saving, the sortable cards remain draggable. If a user initiates a second drag-and-drop reorder while an API call is in flight, both optimistic updates and API calls will race, potentially leading to inconsistent state.

Consider disabling drag interactions while saving is true by passing a disabled prop to SortableDepartmentCard or conditionally removing the listeners spread.

🛡️ Proposed fix to disable dragging during save
 function SortableDepartmentCard({
   dept,
   health,
   agentCount,
   onClick,
+  disabled,
 }: {
   dept: Department
   health: DepartmentHealth | null
   agentCount: number
   onClick: () => void
+  disabled?: boolean
 }) {
   const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
     id: dept.name,
     data: { dept },
+    disabled,
   })
   // ...
 }

 // In the render:
 <SortableDepartmentCard
   dept={dept}
   health={healthMap.get(dept.name) ?? null}
   agentCount={getAgentCount(dept.name)}
   onClick={() => setEditDept(dept)}
+  disabled={saving}
 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 194 - 206, Drag
reordering remains enabled during the saving state, causing race conditions;
update the render loop to pass a disabled={saving} (or similar) prop into
SortableDepartmentCard (from DepartmentsTab) and update SortableDepartmentCard
to stop attaching sortable drag handlers when disabled (e.g., conditionally
spread listeners/attributes only when !disabled) so users cannot start a second
drag while an API call is in flight.
🤖 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/stores/company.ts`:
- Around line 228-248: In reorderAgents: combine the two consecutive set() calls
into a single atomic update so state changes are applied together; specifically,
when prev (from get().config) exists build the new config with the updated
departments (replacing department where d.name === deptName with updatedDept)
and call set once to update both config and saving:false (and ensure saveError
remains null if needed), replacing the current separate set({ config: ... }) and
later set({ saving: false }) calls.

---

Duplicate comments:
In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 194-206: Drag reordering remains enabled during the saving state,
causing race conditions; update the render loop to pass a disabled={saving} (or
similar) prop into SortableDepartmentCard (from DepartmentsTab) and update
SortableDepartmentCard to stop attaching sortable drag handlers when disabled
(e.g., conditionally spread listeners/attributes only when !disabled) so users
cannot start a second drag while an API call is in flight.
🪄 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: ec84f5b4-c9ce-490c-85df-ac11e32ef600

📥 Commits

Reviewing files that changed from the base of the PR and between d34087e and 41c0d06.

📒 Files selected for processing (4)
  • web/src/__tests__/stores/company.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
📜 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). (6)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/__tests__/stores/company.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/__tests__/stores/company.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/stores/company.test.ts
🧠 Learnings (8)
📓 Common learnings
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/).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/__tests__/stores/company.test.ts
📚 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__/stores/company.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/DepartmentEditDrawer.tsx

[warning] 39-39:
Do not call the 'set' function 'setBudgetPercent' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 38-38:
Do not call the 'set' function 'setDisplayName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (19)
web/src/pages/org-edit/DepartmentEditDrawer.tsx (5)

36-42: The batched state updates pattern is acceptable here.

The static analysis warning about calling multiple set functions in an effect is a false positive in this context. React 18+ batches these updates automatically within the same effect, and this is the standard pattern for syncing local form state with a prop-driven entity. The effect correctly reinitializes all form fields atomically when department changes.

The past review comments about budgetPercent initialization (now using department.budget_percent) and the fallback for displayName have been properly addressed.


1-19: Imports and props interface are well-structured.

The component correctly reuses existing UI components (Drawer, InputField, DeptHealthBar, ConfirmDialog, Button) from @/components/ui/ and follows the coding guidelines. Props interface is clearly typed with proper callback signatures.


73-90: Drawer title and DeptHealthBar rendering are correct.

The title fallback (department.display_name ?? department.name) and unconditional DeptHealthBar rendering (with nullable health prop) properly address the previous review comments. The health bar will show "N/A" when health data is unavailable.


121-137: Button actions and loading states are properly wired.

The Save button correctly disables during saving and shows a spinner. The Delete button opens the confirmation dialog appropriately. The Cancel button closes the drawer without side effects.


142-151: ConfirmDialog properly handles the delete flow.

The dialog title uses the correct fallback chain (display_name ?? name ?? 'department'), the loading prop is wired to deleting state, and the onConfirm callback triggers the async delete handler.

web/src/stores/company.ts (4)

65-73: fetchCompanyData now rethrows for proper error propagation.

This change enables callers (like updateFromWsEvent) to handle failures appropriately. The store still sets the error state before rethrowing, so UI consumers can display the error.


76-94: Separate healthError state improves error handling granularity.

Using a dedicated healthError field instead of the general error field allows the UI to distinguish between config fetch failures and health data unavailability. This is a good separation of concerns.


252-273: Targeted rollback for department reordering preserves concurrent changes.

The rollback function now restores only the department ordering while preserving any departments added concurrently during the in-flight request. This addresses the previous review comment about rollback erasing unrelated live updates.


276-320: Optimistic agent reordering preserves original array positions.

The implementation uses in-place replacement (lines 293-298) instead of appending reordered agents at the end. This addresses the previous review comment and prevents visual jumps in UIs that iterate over config.agents in array order.

web/src/__tests__/stores/company.test.ts (4)

61-71: Well-structured resetStore helper ensures clean test isolation.

The helper resets all store fields including the new healthError, saving, and saveError states. This prevents test pollution and ensures each test starts with a known state.


121-137: Good coverage for healthError state transitions.

These tests verify that healthError is set when all health fetches fail and cleared on subsequent success. This matches the store's updated behavior for health-specific error handling.


299-314: createAgent test validates the correct request shape.

The test calls createAgent with the required fields (name, role, department, level) matching the CreateAgentOrgRequest interface. The assertion verifies the agent is appended to the config.


363-405: Tests properly verify optimistic reorder helpers with rollback functionality.

The tests cover the immediate reordering effects, rollback mechanics, and edge cases (null config no-op). TypeScript configuration targets ES2025, so toReversed() on line 403 is fully supported without compatibility concerns.

web/src/pages/org-edit/DepartmentsTab.tsx (6)

106-109: Keyboard sensor properly configured for sortable accessibility.

The KeyboardSensor now includes sortableKeyboardCoordinates as the coordinate getter, enabling proper keyboard-based reordering. This addresses the previous review comment.


187-192: DnD context properly handles cancellation.

The onDragCancel callback now clears activeDept, ensuring the drag overlay disappears when the user cancels (e.g., by pressing Escape). This addresses the previous review comment.


209-215: Drag overlay uses design tokens correctly.

The overlay uses border-accent, bg-card, p-card semantic classes and var(--so-shadow-card-hover) for the shadow, properly following the coding guidelines. The display name fallback (activeDept.display_name ?? activeDept.name) is also correct.


157-162: "Add Department" buttons correctly disabled during saving.

Both instances of the "Add Department" button (empty state at line 158 and main view at line 181) pass disabled={saving}, preventing new department creation while a mutation is in flight.

Also applies to: 180-185


65-72: Card click handling preserves drag functionality.

The onKeyDown stopPropagation on the button prevents keyboard events (Enter/Space) from bubbling to the drag listeners when the button is focused, allowing the edit action to work correctly. The drag attributes and listeners remain on the outer div, so pointer/touch dragging still functions.


74-86: Department card content uses semantic classes and reusable components.

The SectionCard and DeptHealthBar components are properly reused from @/components/ui/. The health prop is passed as nullable (health?.health_percent), allowing the health bar to display "N/A" when data is unavailable. Text styling uses semantic classes (text-xs, text-text-secondary).

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 21:51 — with GitHub Actions Inactive
@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 2

♻️ Duplicate comments (1)
web/src/pages/org-edit/DepartmentsTab.tsx (1)

171-176: ⚠️ Potential issue | 🟠 Major

The create dialog still bypasses the saving guard.

The buttons are disabled, but both DepartmentCreateDialog instances still mount with the raw onCreateDepartment callback and no pending-state guard. If the modal is already open when another mutation starts, this tab can still submit a second create request. Thread saving into the dialog and block submit while pending.

Also applies to: 222-227

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

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 171 - 176, The
DepartmentCreateDialog instances mount without respecting the current pending
save state, so pass the component the tab's saving state and block submits:
thread the saving boolean into both DepartmentCreateDialog props (where
createOpen/setCreateOpen and onCreateDepartment are used) and either have the
dialog use that prop to disable its submit button or wrap/replace
onCreateDepartment with a guard that returns early when saving is true; update
both occurrences (the instance around createOpen and the one at lines ~222-227)
to use the same saving-aware behavior.
🤖 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/stores/company.ts`:
- Around line 35-36: The store's single boolean "saving" is not
concurrency-safe; replace it with an in-flight counter or request token scheme
(e.g., change saving: boolean to inFlightCount: number and expose a derived
isSaving getter) and update all places that currently set "saving = true" /
"saving = false" (and that touch "saveError") to instead increment the counter
on request start and decrement on completion (and only clear saveError or
compute isSaving when inFlightCount reaches zero); ensure decrements never go
below zero and propagate/save individual request errors into saveError (or a
per-request errors map) so overlapping saves don't clobber each other's state.
- Around line 228-243: The success handler in reorderAgents only replaces the
matching department in config.departments, so any concurrent updates that
replaced config.agents will overwrite the optimistic reorder; update the saved
config to also replace the agents list for that department. Specifically, in
reorderAgents (after apiReorderAgents returns updatedDept and when building the
new config from prev via get()/set()), set config.agents so that agents for
deptName are replaced with updatedDept.agents (or orderedIds) while keeping
other config fields intact—i.e., patch both departments and the top-level
config.agents to reflect the successful reorder.

---

Duplicate comments:
In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 171-176: The DepartmentCreateDialog instances mount without
respecting the current pending save state, so pass the component the tab's
saving state and block submits: thread the saving boolean into both
DepartmentCreateDialog props (where createOpen/setCreateOpen and
onCreateDepartment are used) and either have the dialog use that prop to disable
its submit button or wrap/replace onCreateDepartment with a guard that returns
early when saving is true; update both occurrences (the instance around
createOpen and the one at lines ~222-227) to use the same saving-aware behavior.
🪄 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: 87ab4a19-c650-47ac-9c5c-1a78f0c9ed10

📥 Commits

Reviewing files that changed from the base of the PR and between 41c0d06 and ad1a2f0.

📒 Files selected for processing (2)
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/stores/company.ts
🧠 Learnings (6)
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Always reuse existing components from web/src/components/ui/ before creating new ones

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx

Comment on lines +228 to +243
reorderAgents: async (deptName, orderedIds) => {
set({ saving: true, saveError: null })
try {
const updatedDept = await apiReorderAgents(deptName, { agent_ids: orderedIds })
const prev = get().config
set({
saving: false,
...(prev ? {
config: {
...prev,
departments: prev.departments.map((d) =>
d.name === deptName ? updatedDept : d,
),
},
} : {}),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Successful agent reorders can snap back after a background refresh.

This page polls and applies WebSocket refreshes. If config.agents is replaced while the reorder request is in flight, the optimistic order is lost; this success path only patches departments, so it never re-commits orderedIds into config.agents.

💡 Minimal fix
   reorderAgents: async (deptName, orderedIds) => {
     set({ saving: true, saveError: null })
     try {
       const updatedDept = await apiReorderAgents(deptName, { agent_ids: orderedIds })
       const prev = get().config
+      const idSet = new Set(orderedIds)
       set({
         saving: false,
         ...(prev ? {
           config: {
             ...prev,
+            agents: (() => {
+              const agentMap = new Map(
+                prev.agents
+                  .filter((a) => a.department === deptName && idSet.has(a.id))
+                  .map((a) => [a.id, a]),
+              )
+              let idx = 0
+              const reordered = orderedIds
+                .map((id) => agentMap.get(id))
+                .filter((a): a is AgentConfig => a !== undefined)
+              return prev.agents.map((a) =>
+                a.department === deptName && idSet.has(a.id)
+                  ? (reordered[idx++] ?? a)
+                  : a,
+              )
+            })(),
             departments: prev.departments.map((d) =>
               d.name === deptName ? updatedDept : d,
             ),
           },
         } : {}),
       })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/company.ts` around lines 228 - 243, The success handler in
reorderAgents only replaces the matching department in config.departments, so
any concurrent updates that replaced config.agents will overwrite the optimistic
reorder; update the saved config to also replace the agents list for that
department. Specifically, in reorderAgents (after apiReorderAgents returns
updatedDept and when building the new config from prev via get()/set()), set
config.agents so that agents for deptName are replaced with updatedDept.agents
(or orderedIds) while keeping other config fields intact—i.e., patch both
departments and the top-level config.agents to reflect the successful reorder.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 22:48 — 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: 12

♻️ Duplicate comments (3)
web/src/pages/org-edit/AgentsTab.tsx (1)

121-126: ⚠️ Potential issue | 🟠 Major

Gate agent creation and reordering while saving is true.

AgentsTab accepts saving, but the add buttons, drag-and-drop surface, and create dialog stay live. That still allows overlapping creates/reorders while another mutation is in flight, which can race the optimistic state updates.

Also applies to: 190-205, 219-224, 251-265

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

In `@web/src/pages/org-edit/AgentsTab.tsx` around lines 121 - 126, AgentsTab
accepts a saving prop but doesn't prevent user actions while saving; update the
component so when saving is true it disables the add buttons, prevents opening
the CreateAgentDialog, and disables the drag-and-drop surface, and also guard
the handlers onCreateAgent, onReorderAgents, and optimisticReorderAgents so they
are no-ops while saving to avoid overlapping mutations and optimistic races;
specifically, gate the add-button click handlers and the dialog open logic, pass
a disabled prop into the drag-drop container (or skip rendering
DragDropContext/Draggable/ Droppable when saving), and early-return in the
exported onCreateAgent/onReorderAgents/optimisticReorderAgents callbacks when
saving is true.
web/src/pages/org-edit/DepartmentsTab.tsx (1)

223-228: ⚠️ Potential issue | 🟠 Major

The populated-state create dialog is still active during saves.

The empty-state dialog is disabled, but this instance is not. If the dialog is already open when saving flips to true, users can still submit another create request from here.

🩹 Minimal fix
       <DepartmentCreateDialog
         open={createOpen}
         onOpenChange={setCreateOpen}
         existingNames={config.departments.map((d) => d.name)}
         onCreate={onCreateDepartment}
+        disabled={saving}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 223 - 228, The create
dialog remains interactive during saves; update the DepartmentCreateDialog usage
(component name DepartmentCreateDialog, props open={createOpen} and
onCreate={onCreateDepartment}) to prevent submissions while a save is in
progress by wiring the saving state into it—either pass a disabled/isSaving prop
(e.g., disabled={saving} or isSaving={saving}) so the dialog UI/submit button is
disabled, or early-return inside onCreateDepartment when saving is true to
no-op; ensure the dialog cannot trigger a new create while saving.
web/src/stores/company.ts (1)

228-243: ⚠️ Potential issue | 🟠 Major

Persist the confirmed reorder into config.agents too.

AgentsTab renders from the top-level config.agents array, but this success path only replaces the department object. If polling or a WebSocket refresh overwrites the optimistic array before the request settles, the UI can snap back even though the reorder succeeded.

🩹 Keep the top-level agents array in sync
   reorderAgents: async (deptName, orderedIds) => {
     set((s) => ({ savingCount: s.savingCount + 1, saveError: null }))
     try {
       const updatedDept = await apiReorderAgents(deptName, { agent_ids: orderedIds })
       const prev = get().config
+      const idSet = new Set(orderedIds)
       set((s) => ({
         savingCount: Math.max(0, s.savingCount - 1),
         ...(prev ? {
           config: {
             ...prev,
+            agents: (() => {
+              const agentMap = new Map(
+                prev.agents
+                  .filter((a) => a.department === deptName && idSet.has(a.id))
+                  .map((a) => [a.id, a]),
+              )
+              let index = 0
+              const reorderedAgents = orderedIds
+                .map((id) => agentMap.get(id))
+                .filter((a): a is AgentConfig => a !== undefined)
+              return prev.agents.map((a) =>
+                a.department === deptName && idSet.has(a.id)
+                  ? (reorderedAgents[index++] ?? a)
+                  : a,
+              )
+            })(),
             departments: prev.departments.map((d) =>
               d.name === deptName ? updatedDept : d,
             ),
           },
         } : {}),
       }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/company.ts` around lines 228 - 243, The success branch of
reorderAgents updates only config.departments and forgets to sync the top-level
config.agents array, causing the UI to revert if a refresh arrives; in
reorderAgents (use symbols: reorderAgents, apiReorderAgents, updatedDept,
config, prev) update the config object to also replace the top-level agents
ordering by merging updatedDept.agents into prev.agents (e.g. produce a new
agents array that keeps agents not in the department and inserts/overwrites
agents from updatedDept.agents in the new order) when setting state so both
departments and config.agents reflect the confirmed reorder.
🤖 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/__tests__/helpers/factories.ts`:
- Around line 77-84: The factory makeDepartment currently forces the name into
Department['name'] with a type assertion which hides invalid values; remove the
assertion and either change the function signature to accept name:
Department['name'] for strict typing or keep name: string but remove the "as
Department['name']" cast and add a clear comment/docstring stating the factory
is intentionally permissive for tests (and optionally add runtime validation if
you want to fail on invalid names). Ensure references to name in makeDepartment
(including display_name and teams) use the updated parameter type and that
overrides still spread correctly.

In `@web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx`:
- Around line 83-95: The test currently picks the confirm button by grabbing the
last element from screen.getAllByText('Delete'), which is brittle; update the
test in AgentEditDrawer.test.tsx to query the opened confirmation dialog first
(e.g., use screen.getByRole('alertdialog') or getByText('Delete alice?')), then
use within(dialog).getByRole('button', { name: /delete/i }) to click the confirm
button, and ensure you import within from '@testing-library/react' at the top;
keep the rest of the assertions (await waitFor(() =>
expect(mockOnDelete).toHaveBeenCalledWith('alice'))) unchanged.

In `@web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx`:
- Around line 14-27: The health-resolution logic in renderDrawer is more complex
than needed; change the function signature to accept a default empty props
object (e.g., renderDrawer(props: { department?: typeof dept | null; health?:
typeof health | null } = {})) then destructure with defaults (e.g., const {
department = dept, health: healthProp } = props) and determine resolvedHealth by
checking whether health was provided (e.g., use props.hasOwnProperty('health') ?
(healthProp ?? null) : health) before passing department and resolvedHealth into
DepartmentEditDrawer; this keeps the explicit “health not passed vs explicitly
null” behavior but simplifies the code and references props.health, dept,
health, renderDrawer, and DepartmentEditDrawer to locate the change.

In `@web/src/components/ui/dept-health-bar.tsx`:
- Line 64: The span fallback label uses the awkward string 'N/A tasks'; update
the conditional in the DeptHealthBar component so when taskCount is
null/undefined it shows a concise placeholder (e.g., 'N/A' or '—') instead of
'N/A tasks' — locate the expression rendering taskCount (the span with
`{taskCount != null ? \`${taskCount} ...\` : 'N/A tasks'}`) and replace the
fallback branch with your chosen placeholder for consistency with the health
display.

In `@web/src/hooks/useOrgEditData.ts`:
- Around line 71-82: Convert the mount-only useEffect into an async function
that awaits fetchCompanyData() inside a try/catch so any thrown error is
handled, only calls fetchDepartmentHealths() if
useCompanyStore.getState().config is truthy, and only calls polling.start() if
the component is still mounted (use a local "mounted" flag set in the effect
cleanup to prevent starting polling after unmount); ensure polling.stop() is
still invoked in cleanup and log or handle caught errors from fetchCompanyData()
instead of letting the promise escape.

In `@web/src/pages/org-edit/AgentCreateDialog.tsx`:
- Around line 35-71: The dialog currently preserves form state between opens; to
reset when the dialog is closed, add an effect in AgentCreateDialog that watches
the open prop and when open becomes false resets form and related state: call
setForm(INITIAL_FORM), setErrors({}), setSubmitError(null) (and optionally
setSubmitting(false)) so the next open shows a fresh form; this ensures
updateField and handleSubmit continue to work unchanged and references to
INITIAL_FORM, setForm, setErrors, setSubmitError and the open prop make the
change easy to locate.

In `@web/src/pages/org-edit/AgentEditDrawer.stories.tsx`:
- Around line 52-56: Add a new story export named Closed to demonstrate the
drawer's closed state; create export const Closed: Story with args set to {
open: false } (matching the pattern used by Open and Saving) so the component
library shows the closed drawer state alongside Open and Saving.

In `@web/src/pages/org-edit/AgentsTab.tsx`:
- Around line 186-208: The current early return hides department context
whenever config.agents.length === 0; change the condition to only render the
global empty state when there are no agents AND no departments (e.g.
agents.length === 0 && (config?.departments?.length ?? 0) === 0). Keep the Add
Agent button and AgentCreateDialog usage (createOpen, setCreateOpen,
onCreateAgent, departments) but allow the normal render path to run when
departments exist so agentsByDept and DepartmentAgentsSection can display empty
department sections.

In `@web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx`:
- Around line 5-9: The story's stubDept uses name 'engineering' which conflicts
with the existingNames validation and can make the story's onCreate (success)
behavior misleading; update the stubDept object in
DepartmentCreateDialog.stories (symbol: stubDept) to use a non-conflicting name
(e.g., 'marketing' or any name not present in the existingNames array) so the
dialog can be submitted successfully in Storybook and the onCreate stub reflects
a valid create flow.

In `@web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx`:
- Around line 43-47: Add a "Saving" story to demonstrate the drawer's loading
state by exporting a new Story named Saving with args including saving: true
(similar to existing Open and NoHealthData stories); locate the stories in
DepartmentEditDrawer.stories.tsx where Open and NoHealthData are defined and add
the Saving export (export const Saving: Story = { args: { saving: true } }) so
the component's saving prop is covered in Storybook.

In `@web/src/pages/org-edit/DepartmentEditDrawer.tsx`:
- Around line 44-57: The handleSave function allows out-of-range budget_percent
because it only checks Number.isFinite(pct); update it to mirror
DepartmentCreateDialog's validation by ensuring pct is between 0 and 100 before
calling onUpdate: compute pct = Number(budgetPercent), if not
Number.isFinite(pct) treat as undefined, but if finite and (pct < 0 || pct >
100) setSubmitError with a clear validation message and return early (do not
call onUpdate/onClose); reference handleSave and the budget_percent field when
applying this change.

In `@web/src/pages/org-edit/DepartmentsTab.stories.tsx`:
- Around line 51-52: Replace the bogus "as never" returns in the DepartmentsTab
story callbacks with real mock Department objects: update onCreateDepartment and
onUpdateDepartment to return a Promise resolving to a proper mock department
(same shape used in AgentsTab.stories.tsx) including required fields like id,
name, slug, orgId and timestamps so TypeScript and consumers get realistic data;
mirror the mock construction pattern from AgentsTab.stories.tsx to ensure types
align with the Department type and story expectations.

---

Duplicate comments:
In `@web/src/pages/org-edit/AgentsTab.tsx`:
- Around line 121-126: AgentsTab accepts a saving prop but doesn't prevent user
actions while saving; update the component so when saving is true it disables
the add buttons, prevents opening the CreateAgentDialog, and disables the
drag-and-drop surface, and also guard the handlers onCreateAgent,
onReorderAgents, and optimisticReorderAgents so they are no-ops while saving to
avoid overlapping mutations and optimistic races; specifically, gate the
add-button click handlers and the dialog open logic, pass a disabled prop into
the drag-drop container (or skip rendering DragDropContext/Draggable/ Droppable
when saving), and early-return in the exported
onCreateAgent/onReorderAgents/optimisticReorderAgents callbacks when saving is
true.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 223-228: The create dialog remains interactive during saves;
update the DepartmentCreateDialog usage (component name DepartmentCreateDialog,
props open={createOpen} and onCreate={onCreateDepartment}) to prevent
submissions while a save is in progress by wiring the saving state into
it—either pass a disabled/isSaving prop (e.g., disabled={saving} or
isSaving={saving}) so the dialog UI/submit button is disabled, or early-return
inside onCreateDepartment when saving is true to no-op; ensure the dialog cannot
trigger a new create while saving.

In `@web/src/stores/company.ts`:
- Around line 228-243: The success branch of reorderAgents updates only
config.departments and forgets to sync the top-level config.agents array,
causing the UI to revert if a refresh arrives; in reorderAgents (use symbols:
reorderAgents, apiReorderAgents, updatedDept, config, prev) update the config
object to also replace the top-level agents ordering by merging
updatedDept.agents into prev.agents (e.g. produce a new agents array that keeps
agents not in the department and inserts/overwrites agents from
updatedDept.agents in the new order) when setting state so both departments and
config.agents reflect the confirmed reorder.
🪄 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: c1ef90ca-0702-4b9c-8d64-fa2a88cbe7ad

📥 Commits

Reviewing files that changed from the base of the PR and between ad1a2f0 and 76e5d55.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • CLAUDE.md
  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
  • web/package.json
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/types.ts
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/hooks/useOrgEditData.ts
  • web/src/lib/utils.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org/build-org-tree.ts
  • web/src/stores/company.ts
  • web/src/utils/yaml.ts
📜 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). (6)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript, use Pydantic v2 models for Python
Always use async/await for promises; prefer asyncio.TaskGroup for parallel operations in Python
Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files

Files:

  • web/src/pages/org/build-org-tree.ts
  • web/src/lib/utils.ts
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/utils/yaml.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/api/types.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable names, function names, and identifiers

Files:

  • web/src/pages/org/build-org-tree.ts
  • web/src/lib/utils.ts
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/utils/yaml.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/api/types.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Functions should be under 50 lines; files should be under 800 lines

Files:

  • web/src/pages/org/build-org-tree.ts
  • web/src/lib/utils.ts
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/utils/yaml.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/api/types.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Files:

  • web/src/pages/org/build-org-tree.ts
  • web/src/lib/utils.ts
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/utils/yaml.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/api/types.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update relevant docs/design/ pages whenever an approved deviation from the spec occurs during implementation

Files:

  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use real vendor names in documentation, use generic names instead - vendor names only appear in design pages, .claude/ files, third-party imports, and provider presets

Files:

  • docs/design/brand-and-ux.md
  • CLAUDE.md
  • docs/design/page-structure.md
web/src/components/ui/**

📄 CodeRabbit inference engine (CLAUDE.md)

Create a .stories.tsx file alongside every new UI component with all states (default, hover, loading, error, empty)

Files:

  • web/src/components/ui/dept-health-bar.tsx
web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always reuse existing components from web/src/components/ui/ (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Files:

  • web/src/components/ui/dept-health-bar.tsx
🧠 Learnings (29)
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Applied to files:

  • docs/design/brand-and-ux.md
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • web/package.json
  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • web/package.json
  • CLAUDE.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.md : Do not use real vendor names in documentation, use generic names instead - vendor names only appear in design pages, `.claude/` files, third-party imports, and provider presets

Applied to files:

  • CLAUDE.md
📚 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:

  • CLAUDE.md
  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to docs/design/**/*.md : Update relevant `docs/design/` pages whenever an approved deviation from the spec occurs during implementation

Applied to files:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/pages/**/*.{tsx} : Lazy-load page components using React Router; place page-scoped sub-components in `pages/<page-name>/` subdirectories

Applied to files:

  • CLAUDE.md
  • web/src/pages/OrgEditPage.tsx
📚 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:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to pyproject.toml : Dependencies: all versions use == in pyproject.toml. Groups: test (pytest + plugins, hypothesis), dev (includes test + ruff, mypy, pre-commit, commitizen, pip-audit). Required: mem0ai (Mem0 memory backend — the default and currently only backend). Install: uv sync installs everything (dev group is default).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T12:54:22.557Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Go 1.26+ required; CLI dependencies in `cli/go.mod` (Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Applies to cli/go.mod : CLI uses Go 1.26+. Dependencies: Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to cli/**/*.go : Go CLI (Go 1.26+) uses Cobra for commands, charmbracelet/huh for interactive CLI, charmbracelet/lipgloss for styled output. Cross-platform builds (linux/darwin/windows × amd64/arm64). GoReleaser for releases with cosign keyless signing of checksums.txt. SLSA L3 provenance attestations via actions/attest-build-provenance.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • CLAUDE.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 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:

  • CLAUDE.md
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to tests/**/*.py : Never use real vendor names (Anthropic, OpenAI, Claude, GPT) - use generic names like `test-provider`, `test-small-001` in tests and project code

Applied to files:

  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/ui/** : Create a `.stories.tsx` file alongside every new UI component with all states (default, hover, loading, error, empty)

Applied to files:

  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
📚 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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/page-structure.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/page-structure.md
📚 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/page-structure.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx} : Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentEditDrawer.tsx

[warning] 56-56:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 49-49:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

web/src/pages/org-edit/DepartmentEditDrawer.tsx

[warning] 38-38:
Do not call the 'set' function 'setDisplayName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

Comment on lines +77 to +84
export function makeDepartment(name: string, overrides?: Partial<Department>): Department {
return {
name: name as Department['name'],
display_name: name.charAt(0).toUpperCase() + name.slice(1),
teams: [],
...overrides,
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Type assertion on name may mask type errors.

The assertion name as Department['name'] bypasses type checking. If Department['name'] is a union of string literals (e.g., 'engineering' | 'product'), passing an arbitrary string will compile but could cause runtime issues in code that expects only valid department names.

Consider either:

  1. Accepting Department['name'] as the parameter type for type safety
  2. Documenting that this factory is intentionally permissive for testing edge cases
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/helpers/factories.ts` around lines 77 - 84, The factory
makeDepartment currently forces the name into Department['name'] with a type
assertion which hides invalid values; remove the assertion and either change the
function signature to accept name: Department['name'] for strict typing or keep
name: string but remove the "as Department['name']" cast and add a clear
comment/docstring stating the factory is intentionally permissive for tests (and
optionally add runtime validation if you want to fail on invalid names). Ensure
references to name in makeDepartment (including display_name and teams) use the
updated parameter type and that overrides still spread correctly.

Comment on lines +83 to +95
it('calls onDelete with agent name after confirming delete', async () => {
renderDrawer()
fireEvent.click(screen.getByText('Delete'))
// Confirmation dialog opens
expect(screen.getByText('Delete alice?')).toBeInTheDocument()
// Click the destructive confirm button inside the dialog
const confirmButtons = screen.getAllByText('Delete')
// The last "Delete" button is the confirm button in the dialog
fireEvent.click(confirmButtons[confirmButtons.length - 1]!)
await waitFor(() => {
expect(mockOnDelete).toHaveBeenCalledWith('alice')
})
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Fragile confirm button selection.

Finding the confirm button by selecting the last element from getAllByText('Delete') (line 89-91) is brittle—if the dialog structure changes, this could select the wrong button. Consider using a more specific selector like getByRole('alertdialog') followed by within().getByRole('button', { name: /delete/i }) or adding a test ID to the confirm button.

♻️ Proposed fix
-    const confirmButtons = screen.getAllByText('Delete')
-    // The last "Delete" button is the confirm button in the dialog
-    fireEvent.click(confirmButtons[confirmButtons.length - 1]!)
+    // Target the confirm button within the dialog specifically
+    const dialog = screen.getByRole('alertdialog')
+    fireEvent.click(within(dialog).getByRole('button', { name: /delete/i }))

Note: You'll need to import within from @testing-library/react at line 1.

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

In `@web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx` around lines 83 -
95, The test currently picks the confirm button by grabbing the last element
from screen.getAllByText('Delete'), which is brittle; update the test in
AgentEditDrawer.test.tsx to query the opened confirmation dialog first (e.g.,
use screen.getByRole('alertdialog') or getByText('Delete alice?')), then use
within(dialog).getByRole('button', { name: /delete/i }) to click the confirm
button, and ensure you import within from '@testing-library/react' at the top;
keep the rest of the assertions (await waitFor(() =>
expect(mockOnDelete).toHaveBeenCalledWith('alice'))) unchanged.

Comment on lines +14 to +27
function renderDrawer(props?: { department?: typeof dept | null; health?: typeof health | null }) {
const resolvedHealth = props && 'health' in props ? (props.health ?? null) : health
return render(
<DepartmentEditDrawer
open={true}
onClose={mockOnClose}
department={props?.department ?? dept}
health={resolvedHealth}
onUpdate={mockOnUpdate}
onDelete={mockOnDelete}
saving={false}
/>,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider simplifying the health prop resolution logic.

The conditional at line 15 is complex: props && 'health' in props ? (props.health ?? null) : health. This distinguishes between "health not passed" (use default) and "health explicitly set to null" (use null). While correct, a simpler pattern using an explicit default parameter object could improve readability.

♻️ Optional simplification
-  function renderDrawer(props?: { department?: typeof dept | null; health?: typeof health | null }) {
-    const resolvedHealth = props && 'health' in props ? (props.health ?? null) : health
+  function renderDrawer(overrides: { department?: typeof dept | null; health?: typeof health | null } = {}) {
+    const resolvedHealth = 'health' in overrides ? overrides.health : health
     return render(
       <DepartmentEditDrawer
         open={true}
         onClose={mockOnClose}
-        department={props?.department ?? dept}
+        department={overrides.department ?? dept}
         health={resolvedHealth}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function renderDrawer(props?: { department?: typeof dept | null; health?: typeof health | null }) {
const resolvedHealth = props && 'health' in props ? (props.health ?? null) : health
return render(
<DepartmentEditDrawer
open={true}
onClose={mockOnClose}
department={props?.department ?? dept}
health={resolvedHealth}
onUpdate={mockOnUpdate}
onDelete={mockOnDelete}
saving={false}
/>,
)
}
function renderDrawer(overrides: { department?: typeof dept | null; health?: typeof health | null } = {}) {
const resolvedHealth = 'health' in overrides ? overrides.health : health
return render(
<DepartmentEditDrawer
open={true}
onClose={mockOnClose}
department={overrides.department ?? dept}
health={resolvedHealth}
onUpdate={mockOnUpdate}
onDelete={mockOnDelete}
saving={false}
/>,
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx` around lines
14 - 27, The health-resolution logic in renderDrawer is more complex than
needed; change the function signature to accept a default empty props object
(e.g., renderDrawer(props: { department?: typeof dept | null; health?: typeof
health | null } = {})) then destructure with defaults (e.g., const { department
= dept, health: healthProp } = props) and determine resolvedHealth by checking
whether health was provided (e.g., use props.hasOwnProperty('health') ?
(healthProp ?? null) : health) before passing department and resolvedHealth into
DepartmentEditDrawer; this keeps the explicit “health not passed vs explicitly
null” behavior but simplifies the code and references props.health, dept,
health, renderDrawer, and DepartmentEditDrawer to locate the change.

Comment on lines +71 to +82
// Initial data fetch (sequential: health depends on config)
useEffect(() => {
const store = useCompanyStore.getState()
store.fetchCompanyData().then(() => {
if (useCompanyStore.getState().config) {
store.fetchDepartmentHealths()
}
polling.start()
})
return () => polling.stop()
// eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only effect; polling ref identity is stable
}, [])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard the bootstrap promise before starting polling.

fetchCompanyData() now rethrows on failure, but this effect never catches it. The same naked promise chain can also call polling.start() after unmount, leaving a timer running off-screen.

🩹 Suggested fix
   useEffect(() => {
     const store = useCompanyStore.getState()
-    store.fetchCompanyData().then(() => {
-      if (useCompanyStore.getState().config) {
-        store.fetchDepartmentHealths()
-      }
-      polling.start()
-    })
-    return () => polling.stop()
+    let cancelled = false
+
+    void (async () => {
+      try {
+        await store.fetchCompanyData()
+        if (cancelled) return
+        if (useCompanyStore.getState().config) {
+          await store.fetchDepartmentHealths()
+        }
+        if (!cancelled) {
+          polling.start()
+        }
+      } catch {
+        // fetchCompanyData already populated store.error
+      }
+    })()
+
+    return () => {
+      cancelled = true
+      polling.stop()
+    }
     // eslint-disable-next-line `@eslint-react/exhaustive-deps` -- mount-only effect; polling ref identity is stable
   }, [])

As per coding guidelines, "Always use async/await for promises".

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

In `@web/src/hooks/useOrgEditData.ts` around lines 71 - 82, Convert the mount-only
useEffect into an async function that awaits fetchCompanyData() inside a
try/catch so any thrown error is handled, only calls fetchDepartmentHealths() if
useCompanyStore.getState().config is truthy, and only calls polling.start() if
the component is still mounted (use a local "mounted" flag set in the effect
cleanup to prevent starting polling after unmount); ensure polling.stop() is
still invoked in cleanup and log or handle caught errors from fetchCompanyData()
instead of letting the promise escape.

Comment on lines +51 to +52
onCreateDepartment: async () => ({}) as never,
onUpdateDepartment: async () => ({}) as never,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid as never in story callbacks; return a real mock department.

Lines 51-52 bypass return types with as never, reducing story reliability. This was flagged and fixed in AgentsTab.stories.tsx — apply the same pattern here by returning a proper mock department.

✅ Suggested fix
-    onCreateDepartment: async () => ({}) as never,
-    onUpdateDepartment: async () => ({}) as never,
+    onCreateDepartment: async () => mockConfig.departments[0],
+    onUpdateDepartment: async () => mockConfig.departments[0],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentsTab.stories.tsx` around lines 51 - 52,
Replace the bogus "as never" returns in the DepartmentsTab story callbacks with
real mock Department objects: update onCreateDepartment and onUpdateDepartment
to return a Promise resolving to a proper mock department (same shape used in
AgentsTab.stories.tsx) including required fields like id, name, slug, orgId and
timestamps so TypeScript and consumers get realistic data; mirror the mock
construction pattern from AgentsTab.stories.tsx to ensure types align with the
Department type and story expectations.

Replace OrgEditPage placeholder with full organization editor at /org/edit.
Three-tab interface (General, Agents, Departments) with CRUD dialogs,
drag-drop reordering via @dnd-kit, YAML editor toggle, and optimistic
updates with rollback. API mutation stubs ready for backend implementation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aureliolo and others added 8 commits March 28, 2026 08:59
- Validate non-empty agent name in AgentEditDrawer before saving
- Remove duplicate "Add Agent" action from empty state
- Stop event propagation on edit buttons to prevent dnd-kit interception
- Add error toasts on drag-drop reorder rollback (both tabs)
- Rethrow in handleYamlSave so YamlEditorPanel keeps dirty flag on failure
- Add YAML save scope hint in editor panel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Targeted optimistic rollbacks: restore only dept/agent ordering, not entire config
- Extract DepartmentAgentsSection component (CLAUDE.md: no >8-line .map() JSX)
- Remove onPointerDown stopPropagation (was blocking drag), keep onKeyDown stop
- Add error state when config fails to load (loading=false, config=null)
- Fix Link/Button nesting: use asChild pattern for valid HTML

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add onDragCancel to both DndContexts to clear overlay on cancel
- DeptHealthBar: accept optional health, show N/A when unavailable
- Pass undefined instead of 0 for missing health data in DepartmentsTab
- Department rollback preserves concurrently added departments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Conditional ARIA meter role: only emit role/aria-valuenow when health is available
- Make taskCount optional in DeptHealthBar, show N/A when unavailable
- Replace hardcoded shadow-lg with design token var(--so-shadow-card-hover)
- Consolidate sequential set() calls into single atomic updates in all mutations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Disable Add Department buttons during saving
- Remove duplicate EmptyState action (same pattern as AgentsTab)
- Render DeptHealthBar unconditionally in drawer (shows N/A when no health data)
- Rethrow in fetchCompanyData so updateFromWsEvent skips dependent fetches on failure
- Update test to expect rethrow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Combine reorderAgents config + saving updates into single set() call
- Disable sortable drag on department cards while saving is in-flight

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace saving: boolean with savingCount: number in company store
- Derive saving via savingCount > 0 in useOrgEditData hook (public API unchanged)
- All mutations increment on start, decrement on end (never below 0)
- Concurrent saves no longer clobber each other's saving state
- Thread disabled prop into DepartmentCreateDialog to block submits during saves
- Update store and hook tests for savingCount

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Handle unhandled rejection in useOrgEditData mount effect (mounted guard)
- Reset AgentCreateDialog form state when dialog closes
- Fix empty state: show dept sections when agents=0 but departments exist
- Disable Add Agent buttons during saving
- Validate budget range (0-100) in DepartmentEditDrawer
- Fix 'N/A tasks' label to just 'N/A' in DeptHealthBar
- Add Closed story for AgentEditDrawer, Saving story for DepartmentEditDrawer
- Fix DepartmentCreateDialog stub name to non-conflicting 'design'
- Replace DepartmentsTab story as-never casts with real mock objects
- Update AgentsTab test for new empty state behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 28, 2026 08:01 — 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: 9

♻️ Duplicate comments (4)
web/src/__tests__/pages/OrgEditPage.test.tsx (1)

34-37: 🧹 Nitpick | 🔵 Trivial

Simplify the mocked hook export shape.

The computed property indirection is unnecessary here; a direct { useOrgEditData: ... } mock is clearer.

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

In `@web/src/__tests__/pages/OrgEditPage.test.tsx` around lines 34 - 37, Replace
the computed property export in the jest mock with a direct property to simplify
the mock shape: change the factory returned object from { [hookName]: () =>
getOrgEditData() } to a direct { useOrgEditData: () => getOrgEditData() } so the
mocked hook (useOrgEditData) is exported plainly and clearly in the test.
web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx (1)

89-91: ⚠️ Potential issue | 🟡 Minor

Use a dialog-scoped selector for the confirm-delete action.

Selecting the last "Delete" match is brittle and can target the wrong button as UI evolves.

Suggested fix
-import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'
@@
-    const confirmButtons = screen.getAllByText('Delete')
-    // The last "Delete" button is the confirm button in the dialog
-    fireEvent.click(confirmButtons[confirmButtons.length - 1]!)
+    const dialog = screen.getByRole('alertdialog')
+    fireEvent.click(within(dialog).getByRole('button', { name: /^delete$/i }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx` around lines 89 -
91, The test currently grabs all "Delete" buttons and clicks the last one which
is brittle; instead scope the selector to the confirmation dialog: locate the
dialog element (e.g., via getByRole('dialog') or getByTestId if present) and use
within(dialog).getByRole('button', { name: /delete/i }) or
within(dialog).getByText('Delete') and then fireEvent.click on that scoped
button; update the code around the confirm click in AgentEditDrawer.test.tsx
where confirmButtons and the final fireEvent.click are used to use the
dialog-scoped selector.
web/src/pages/org-edit/DepartmentsTab.tsx (1)

223-228: ⚠️ Potential issue | 🟡 Minor

Missing disabled={saving} on the main create dialog.

The DepartmentCreateDialog in the empty state branch (line 176) has disabled={saving}, but this instance does not. This allows creating a department while another mutation is in flight.

🩹 Proposed fix
       <DepartmentCreateDialog
         open={createOpen}
         onOpenChange={setCreateOpen}
         existingNames={config.departments.map((d) => d.name)}
         onCreate={onCreateDepartment}
+        disabled={saving}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 223 - 228, The
DepartmentCreateDialog instance is missing the disabled prop so users can start
a new create while a save is in flight; add disabled={saving} to this
DepartmentCreateDialog (the one using props open={createOpen},
onOpenChange={setCreateOpen}, existingNames={config.departments.map((d) =>
d.name)}, onCreate={onCreateDepartment}) so the dialog is disabled while the
saving flag is true.
web/src/hooks/useOrgEditData.ts (1)

71-93: 🧹 Nitpick | 🔵 Trivial

Consider using async/await for the bootstrap effect.

The .then() chain works but doesn't align with the coding guideline "Always use async/await for promises." The mounted flag and error handling are correctly implemented.

♻️ Optional: Convert to async/await
   useEffect(() => {
     let mounted = true
     const store = useCompanyStore.getState()
-    store.fetchCompanyData()
-      .then(() => {
-        if (!mounted) return
-        if (useCompanyStore.getState().config) {
-          return store.fetchDepartmentHealths()
-        }
-      })
-      .then(() => {
-        if (mounted) polling.start()
-      })
-      .catch(() => {
-        // Errors are set in store state by the respective fetch methods
-      })
+    void (async () => {
+      try {
+        await store.fetchCompanyData()
+        if (!mounted) return
+        if (useCompanyStore.getState().config) {
+          await store.fetchDepartmentHealths()
+        }
+        if (mounted) polling.start()
+      } catch {
+        // Errors are set in store state by the respective fetch methods
+      }
+    })()
     return () => {
       mounted = false
       polling.stop()
     }
     // eslint-disable-next-line `@eslint-react/exhaustive-deps` -- mount-only effect; polling ref identity is stable
   }, [])

As per coding guidelines: "Always use async/await for promises".

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

In `@web/src/hooks/useOrgEditData.ts` around lines 71 - 93, The bootstrap
useEffect currently uses a promise .then() chain; convert it to an inner async
function so it uses async/await: create an async function inside the useEffect
that awaits useCompanyStore.getState().fetchCompanyData(), checks the mounted
flag, then if useCompanyStore.getState().config awaits
store.fetchDepartmentHealths(), and on success calls polling.start() if still
mounted; wrap awaits in try/catch (errors are handled by the store) and keep the
same mounted flag/cleanup that sets mounted = false and calls polling.stop() in
the returned cleanup; preserve the existing eslint-disable comment for the
mount-only effect and reference the functions useEffect,
useCompanyStore.getState, fetchCompanyData, fetchDepartmentHealths,
polling.start, and polling.stop when making the change.
🤖 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/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 74-89: Add a test to verify that useOrgEditData calls
fetchDepartmentHealths after config becomes available: mock fetchCompanyData
(mockFetchCompanyData) to set useCompanyStore state with a stubbed config (e.g.,
makeCompanyConfig()) when called, render the hook (useOrgEditData), wait for
mockFetchCompanyData to be called, then wait/assert that
mockFetchDepartmentHealths has been called once; this ensures the conditional
sequential fetch behavior (fetchDepartmentHealths after config) is covered.

In `@web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx`:
- Around line 60-64: Add a test that completes the delete flow by asserting the
delete handler is invoked after confirming: use renderDrawer(), trigger the
delete dialog with fireEvent.click(screen.getByText('Delete')), then simulate
confirming the dialog (e.g., fireEvent.click(screen.getByText('Confirm')) or the
actual confirm button text used by ConfirmDialog), and finally waitFor and
expect(mockOnDelete).toHaveBeenCalledWith('engineering'); reference renderDrawer
and mockOnDelete to locate where to add the test and use
screen.getByText/fireEvent/waitFor as in the existing tests.

In `@web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx`:
- Around line 39-44: The test currently asserts specific department names
("Engineering", "Product") that rely on makeCompanyConfig() defaults; instead,
update the test in DepartmentsTab.test.tsx to use an explicit local config
fixture or the config object used for rendering (e.g., replace hard-coded
expectations with iterating over config.departments and asserting each
department.name appears via renderTab()), so the assertions reference the test's
own fixture rather than implicit factory defaults (use renderTab() with the
local config or read config.departments to drive the expectations).

In `@web/src/__tests__/pages/org-edit/GeneralTab.test.tsx`:
- Around line 17-65: Tests repeatedly call makeCompanyConfig() and
render(<GeneralTab ... />); introduce a local helper (e.g., renderTab) inside
the test file that accepts optional overrides and calls makeCompanyConfig(),
then renders <GeneralTab config={config} onUpdate={mockOnUpdate} saving={saving}
/> and returns the config and any render utilities; replace the repeated setup
in each spec with calls to renderTab so tests reference the same initialization
logic (use identifiers GeneralTab, makeCompanyConfig, mockOnUpdate, and saving
in the helper to locate where to change).

In `@web/src/pages/org-edit/AgentCreateDialog.tsx`:
- Line 81: Memoize the deptOptions array in AgentCreateDialog by wrapping the
current mapping (const deptOptions = departments.map(...)) in a useMemo so the
array is stable across renders; import React.useMemo (or useMemo) if not present
and use departments as the dependency list so deptOptions only recomputes when
departments changes (matching the pattern used in AgentEditDrawer).

In `@web/src/pages/org-edit/AgentsTab.stories.tsx`:
- Around line 100-104: Add a new Story variant that demonstrates the component
while saving by exporting a "Saving" story and passing args: { saving: true };
locate the stories file where Default and Empty are declared (symbols Default,
Empty) and add export const Saving: Story = { args: { saving: true } } so the UI
shows the async/loading state (the component's prop saving is currently defined
as false around the existing stories).

In `@web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx`:
- Around line 5-9: The stubDept object in DepartmentCreateDialog.stories.tsx
should include the optional budget_percent field to better mirror a realistic
Department; update the stubDept constant to add a budget_percent number (e.g.,
20 or 0.2 depending on your app convention) and ensure the value type matches
the Department type so the story and TypeScript typings remain correct.

In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 31-33: The formatBudget function uses toLocaleString() with no
locale, causing inconsistent number formatting across browsers; update
formatBudget to use Intl.NumberFormat with an explicit locale (or accept a
locale from app i18n/user preferences) and a fixed currency/formatting options
so numbers render consistently (reference the formatBudget function to implement
this change).
- Around line 35-117: GeneralTab is >50 lines; extract the form UI and/or state
into a smaller unit to comply with the guideline: create a new
CompanySettingsForm component (props: form, setForm, submitError, saving,
onSave) and move the JSX from the return block into it, leaving GeneralTab to
only initialize form state, run the useEffect that maps config -> form, and pass
handleSave (the existing callback) and state down; alternatively extract form
state and handleSave into a custom hook (useCompanySettingsForm) that returns
{form, setForm, submitError, saving, handleSave} and keep GeneralTab as a thin
wrapper that renders the new CompanySettingsForm. Ensure you keep existing
symbols (GeneralTab, handleSave, useEffect, form, setForm, submitError) and
adjust imports/props accordingly.

---

Duplicate comments:
In `@web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx`:
- Around line 89-91: The test currently grabs all "Delete" buttons and clicks
the last one which is brittle; instead scope the selector to the confirmation
dialog: locate the dialog element (e.g., via getByRole('dialog') or getByTestId
if present) and use within(dialog).getByRole('button', { name: /delete/i }) or
within(dialog).getByText('Delete') and then fireEvent.click on that scoped
button; update the code around the confirm click in AgentEditDrawer.test.tsx
where confirmButtons and the final fireEvent.click are used to use the
dialog-scoped selector.

In `@web/src/__tests__/pages/OrgEditPage.test.tsx`:
- Around line 34-37: Replace the computed property export in the jest mock with
a direct property to simplify the mock shape: change the factory returned object
from { [hookName]: () => getOrgEditData() } to a direct { useOrgEditData: () =>
getOrgEditData() } so the mocked hook (useOrgEditData) is exported plainly and
clearly in the test.

In `@web/src/hooks/useOrgEditData.ts`:
- Around line 71-93: The bootstrap useEffect currently uses a promise .then()
chain; convert it to an inner async function so it uses async/await: create an
async function inside the useEffect that awaits
useCompanyStore.getState().fetchCompanyData(), checks the mounted flag, then if
useCompanyStore.getState().config awaits store.fetchDepartmentHealths(), and on
success calls polling.start() if still mounted; wrap awaits in try/catch (errors
are handled by the store) and keep the same mounted flag/cleanup that sets
mounted = false and calls polling.stop() in the returned cleanup; preserve the
existing eslint-disable comment for the mount-only effect and reference the
functions useEffect, useCompanyStore.getState, fetchCompanyData,
fetchDepartmentHealths, polling.start, and polling.stop when making the change.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 223-228: The DepartmentCreateDialog instance is missing the
disabled prop so users can start a new create while a save is in flight; add
disabled={saving} to this DepartmentCreateDialog (the one using props
open={createOpen}, onOpenChange={setCreateOpen},
existingNames={config.departments.map((d) => d.name)},
onCreate={onCreateDepartment}) so the dialog is disabled while the saving flag
is true.
🪄 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: 5f1d2a33-af03-43bd-9d41-cd6539f1c202

📥 Commits

Reviewing files that changed from the base of the PR and between 76e5d55 and aa3f3cd.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • CLAUDE.md
  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
  • web/package.json
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/stores/company.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/types.ts
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/hooks/useOrgEditData.ts
  • web/src/lib/utils.ts
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org/build-org-tree.ts
  • web/src/stores/company.ts
  • web/src/utils/yaml.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript, use Pydantic v2 models for Python
Always use async/await for promises; prefer asyncio.TaskGroup for parallel operations in Python
Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files

Files:

  • web/src/lib/utils.ts
  • web/src/pages/org/build-org-tree.ts
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/utils/yaml.ts
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/types.ts
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable names, function names, and identifiers

Files:

  • web/src/lib/utils.ts
  • web/src/pages/org/build-org-tree.ts
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/utils/yaml.ts
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/types.ts
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Functions should be under 50 lines; files should be under 800 lines

Files:

  • web/src/lib/utils.ts
  • web/src/pages/org/build-org-tree.ts
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/utils/yaml.ts
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/types.ts
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Files:

  • web/src/lib/utils.ts
  • web/src/pages/org/build-org-tree.ts
  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/OrgEditSkeleton.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/AgentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/utils/yaml.ts
  • web/src/__tests__/pages/org-edit/AgentEditDrawer.test.tsx
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/api/types.ts
  • web/src/components/ui/dept-health-bar.tsx
  • web/src/pages/org-edit/AgentEditDrawer.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.tsx
  • web/src/__tests__/helpers/factories.ts
  • web/src/__tests__/stores/company.test.ts
  • web/src/hooks/useOrgEditData.ts
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/stores/company.ts
  • web/src/api/endpoints/company.ts
  • web/src/pages/org-edit/DepartmentsTab.tsx
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use real vendor names in documentation, use generic names instead - vendor names only appear in design pages, .claude/ files, third-party imports, and provider presets

Files:

  • CLAUDE.md
  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update relevant docs/design/ pages whenever an approved deviation from the spec occurs during implementation

Files:

  • docs/design/brand-and-ux.md
  • docs/design/page-structure.md
web/src/components/ui/**

📄 CodeRabbit inference engine (CLAUDE.md)

Create a .stories.tsx file alongside every new UI component with all states (default, hover, loading, error, empty)

Files:

  • web/src/components/ui/dept-health-bar.tsx
web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always reuse existing components from web/src/components/ui/ (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Files:

  • web/src/components/ui/dept-health-bar.tsx
🧠 Learnings (30)
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • web/package.json
  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • web/package.json
  • CLAUDE.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.md : Do not use real vendor names in documentation, use generic names instead - vendor names only appear in design pages, `.claude/` files, third-party imports, and provider presets

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to docs/design/**/*.md : Update relevant `docs/design/` pages whenever an approved deviation from the spec occurs during implementation

Applied to files:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/pages/**/*.{tsx} : Lazy-load page components using React Router; place page-scoped sub-components in `pages/<page-name>/` subdirectories

Applied to files:

  • CLAUDE.md
  • web/src/pages/OrgEditPage.tsx
📚 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:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 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:

  • CLAUDE.md
  • web/src/pages/org-edit/AgentsTab.tsx
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.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:

  • CLAUDE.md
  • docs/design/page-structure.md
📚 Learning: 2026-03-21T12:54:22.557Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Go 1.26+ required; CLI dependencies in `cli/go.mod` (Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to pyproject.toml : Dependencies: all versions use == in pyproject.toml. Groups: test (pytest + plugins, hypothesis), dev (includes test + ruff, mypy, pre-commit, commitizen, pip-audit). Required: mem0ai (Mem0 memory backend — the default and currently only backend). Install: uv sync installs everything (dev group is default).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Applies to cli/go.mod : CLI uses Go 1.26+. Dependencies: Cobra, charmbracelet/huh, charmbracelet/lipgloss, sigstore-go, go-containerregistry, go-tuf

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to cli/**/*.go : Go CLI (Go 1.26+) uses Cobra for commands, charmbracelet/huh for interactive CLI, charmbracelet/lipgloss for styled output. Cross-platform builds (linux/darwin/windows × amd64/arm64). GoReleaser for releases with cosign keyless signing of checksums.txt. SLSA L3 provenance attestations via actions/attest-build-provenance.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to cli/go.mod : Go CLI dependencies: Go 1.26+, Cobra (commands), charmbracelet/huh (interactive CLI), charmbracelet/lipgloss (styled output).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/ui/** : Create a `.stories.tsx` file alongside every new UI component with all states (default, hover, loading, error, empty)

Applied to files:

  • web/src/pages/org-edit/OrgEditSkeleton.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.stories.tsx
  • web/src/pages/org-edit/AgentCreateDialog.stories.tsx
  • web/src/pages/org-edit/YamlEditorPanel.stories.tsx
  • web/src/pages/org-edit/AgentEditDrawer.stories.tsx
  • web/src/pages/org-edit/DepartmentEditDrawer.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
📚 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__/pages/org-edit/DepartmentsTab.test.tsx
  • web/src/__tests__/pages/OrgEditPage.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentCreateDialog.test.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
  • web/src/__tests__/pages/org-edit/GeneralTab.test.tsx
  • web/src/__tests__/pages/org-edit/YamlEditorPanel.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/utils/yaml.test.ts
  • web/src/__tests__/helpers/factories.ts
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Applied to files:

  • docs/design/brand-and-ux.md
  • web/src/pages/OrgEditPage.tsx
  • web/src/pages/org-edit/AgentsTab.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to tests/**/*.py : Never use real vendor names (Anthropic, OpenAI, Claude, GPT) - use generic names like `test-provider`, `test-small-001` in tests and project code

Applied to files:

  • web/src/__tests__/pages/org-edit/AgentsTab.test.tsx
📚 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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/page-structure.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/page-structure.md
📚 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/page-structure.md
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx} : Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentCreateDialog.tsx

[warning] 45-45:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 44-44:
Do not call the 'set' function 'setErrors' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 43-43:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

Comment on lines +39 to +44
it('renders department cards', () => {
renderTab()
// Each dept name appears in both SectionCard title and DeptHealthBar
expect(screen.getAllByText('Engineering').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('Product').length).toBeGreaterThanOrEqual(1)
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Make assertions less coupled to factory defaults.

These name assertions depend on implicit makeCompanyConfig() output. Prefer asserting against an explicit local config fixture (or iterate over config.departments) to keep failures intent-revealing when factory defaults change.

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

In `@web/src/__tests__/pages/org-edit/DepartmentsTab.test.tsx` around lines 39 -
44, The test currently asserts specific department names ("Engineering",
"Product") that rely on makeCompanyConfig() defaults; instead, update the test
in DepartmentsTab.test.tsx to use an explicit local config fixture or the config
object used for rendering (e.g., replace hard-coded expectations with iterating
over config.departments and asserting each department.name appears via
renderTab()), so the assertions reference the test's own fixture rather than
implicit factory defaults (use renderTab() with the local config or read
config.departments to drive the expectations).

Comment on lines +17 to +65
it('renders company name field with value from config', () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={false} />)
const input = screen.getByLabelText(/company name/i)
expect(input).toHaveValue('Test Corp')
})

it('renders autonomy level select', () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={false} />)
expect(screen.getByLabelText(/autonomy level/i)).toBeInTheDocument()
})

it('renders monthly budget slider', () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={false} />)
expect(screen.getByLabelText(/monthly budget/i)).toBeInTheDocument()
})

it('renders communication pattern field', () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={false} />)
expect(screen.getByLabelText(/communication pattern/i)).toBeInTheDocument()
})

it('renders save button', () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={false} />)
expect(screen.getByText('Save Settings')).toBeInTheDocument()
})

it('calls onUpdate with correct payload when save is clicked', async () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={false} />)
fireEvent.click(screen.getByText('Save Settings'))
expect(mockOnUpdate).toHaveBeenCalledTimes(1)
expect(mockOnUpdate).toHaveBeenCalledWith(expect.objectContaining({
company_name: 'Test Corp',
autonomy_level: 'semi',
budget_monthly: 100,
communication_pattern: 'hybrid',
}))
})

it('disables save button when saving', () => {
const config = makeCompanyConfig()
render(<GeneralTab config={config} onUpdate={mockOnUpdate} saving={true} />)
expect(screen.getByText('Save Settings').closest('button')).toBeDisabled()
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider a local renderTab helper to reduce repetition.

Most tests repeat the same makeCompanyConfig() + render(...) setup. A small helper would reduce duplication and make each test’s intent tighter.

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

In `@web/src/__tests__/pages/org-edit/GeneralTab.test.tsx` around lines 17 - 65,
Tests repeatedly call makeCompanyConfig() and render(<GeneralTab ... />);
introduce a local helper (e.g., renderTab) inside the test file that accepts
optional overrides and calls makeCompanyConfig(), then renders <GeneralTab
config={config} onUpdate={mockOnUpdate} saving={saving} /> and returns the
config and any render utilities; replace the repeated setup in each spec with
calls to renderTab so tests reference the same initialization logic (use
identifiers GeneralTab, makeCompanyConfig, mockOnUpdate, and saving in the
helper to locate where to change).

Comment on lines +35 to +117
export function GeneralTab({ config, onUpdate, saving }: GeneralTabProps) {
const [form, setForm] = useState<FormState>({
company_name: '',
autonomy_level: 'semi',
budget_monthly: 100,
communication_pattern: 'hybrid',
})
const [submitError, setSubmitError] = useState<string | null>(null)

useEffect(() => {
if (config) {
setForm({
company_name: config.company_name,
autonomy_level: config.autonomy_level ?? 'semi',
budget_monthly: config.budget_monthly ?? 100,
communication_pattern: config.communication_pattern ?? 'hybrid',
})
}
}, [config])

const handleSave = useCallback(async () => {
setSubmitError(null)
try {
await onUpdate({
company_name: form.company_name.trim() || undefined,
autonomy_level: form.autonomy_level as UpdateCompanyRequest['autonomy_level'],
budget_monthly: form.budget_monthly,
communication_pattern: form.communication_pattern.trim() || undefined,
})
} catch (err) {
setSubmitError(err instanceof Error ? err.message : 'Failed to save')
}
}, [form, onUpdate])

if (!config) {
return <EmptyState icon={Settings} title="No company data" description="Company configuration is not loaded yet." />
}

return (
<SectionCard title="Company Settings" icon={Settings}>
<div className="space-y-5 max-w-xl">
<InputField
label="Company Name"
value={form.company_name}
onChange={(e) => setForm((prev) => ({ ...prev, company_name: e.target.value }))}
required
/>

<SelectField
label="Autonomy Level"
options={AUTONOMY_OPTIONS}
value={form.autonomy_level}
onChange={(value) => setForm((prev) => ({ ...prev, autonomy_level: value }))}
/>

<SliderField
label="Monthly Budget"
value={form.budget_monthly}
onChange={(value) => setForm((prev) => ({ ...prev, budget_monthly: value }))}
min={0}
max={10000}
step={50}
formatValue={formatBudget}
/>

<InputField
label="Communication Pattern"
value={form.communication_pattern}
onChange={(e) => setForm((prev) => ({ ...prev, communication_pattern: e.target.value }))}
hint="e.g. hybrid, broadcast, hierarchical"
/>

{submitError && (
<p className="text-xs text-danger">{submitError}</p>
)}

<Button onClick={handleSave} disabled={saving}>
{saving && <Loader2 className="mr-2 size-4 animate-spin" />}
Save Settings
</Button>
</div>
</SectionCard>
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Function exceeds 50-line guideline.

The GeneralTab function spans ~82 lines (lines 35-117), exceeding the coding guideline of keeping functions under 50 lines. Consider extracting the form JSX into a separate CompanySettingsForm component or extracting the form state logic into a custom hook.

♻️ Potential refactor approach
+function useCompanyForm(config: CompanyConfig | null) {
+  const [form, setForm] = useState<FormState>({
+    company_name: '',
+    autonomy_level: 'semi',
+    budget_monthly: 100,
+    communication_pattern: 'hybrid',
+  })
+
+  useEffect(() => {
+    if (config) {
+      setForm({
+        company_name: config.company_name,
+        autonomy_level: config.autonomy_level ?? 'semi',
+        budget_monthly: config.budget_monthly ?? 100,
+        communication_pattern: config.communication_pattern ?? 'hybrid',
+      })
+    }
+  }, [config])
+
+  return { form, setForm }
+}

 export function GeneralTab({ config, onUpdate, saving }: GeneralTabProps) {
-  const [form, setForm] = useState<FormState>({...})
-  ...useEffect...
+  const { form, setForm } = useCompanyForm(config)
   // ... rest of component
 }

As per coding guidelines: "Functions should be under 50 lines".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function GeneralTab({ config, onUpdate, saving }: GeneralTabProps) {
const [form, setForm] = useState<FormState>({
company_name: '',
autonomy_level: 'semi',
budget_monthly: 100,
communication_pattern: 'hybrid',
})
const [submitError, setSubmitError] = useState<string | null>(null)
useEffect(() => {
if (config) {
setForm({
company_name: config.company_name,
autonomy_level: config.autonomy_level ?? 'semi',
budget_monthly: config.budget_monthly ?? 100,
communication_pattern: config.communication_pattern ?? 'hybrid',
})
}
}, [config])
const handleSave = useCallback(async () => {
setSubmitError(null)
try {
await onUpdate({
company_name: form.company_name.trim() || undefined,
autonomy_level: form.autonomy_level as UpdateCompanyRequest['autonomy_level'],
budget_monthly: form.budget_monthly,
communication_pattern: form.communication_pattern.trim() || undefined,
})
} catch (err) {
setSubmitError(err instanceof Error ? err.message : 'Failed to save')
}
}, [form, onUpdate])
if (!config) {
return <EmptyState icon={Settings} title="No company data" description="Company configuration is not loaded yet." />
}
return (
<SectionCard title="Company Settings" icon={Settings}>
<div className="space-y-5 max-w-xl">
<InputField
label="Company Name"
value={form.company_name}
onChange={(e) => setForm((prev) => ({ ...prev, company_name: e.target.value }))}
required
/>
<SelectField
label="Autonomy Level"
options={AUTONOMY_OPTIONS}
value={form.autonomy_level}
onChange={(value) => setForm((prev) => ({ ...prev, autonomy_level: value }))}
/>
<SliderField
label="Monthly Budget"
value={form.budget_monthly}
onChange={(value) => setForm((prev) => ({ ...prev, budget_monthly: value }))}
min={0}
max={10000}
step={50}
formatValue={formatBudget}
/>
<InputField
label="Communication Pattern"
value={form.communication_pattern}
onChange={(e) => setForm((prev) => ({ ...prev, communication_pattern: e.target.value }))}
hint="e.g. hybrid, broadcast, hierarchical"
/>
{submitError && (
<p className="text-xs text-danger">{submitError}</p>
)}
<Button onClick={handleSave} disabled={saving}>
{saving && <Loader2 className="mr-2 size-4 animate-spin" />}
Save Settings
</Button>
</div>
</SectionCard>
)
function useCompanyForm(config: CompanyConfig | null) {
const [form, setForm] = useState<FormState>({
company_name: '',
autonomy_level: 'semi',
budget_monthly: 100,
communication_pattern: 'hybrid',
})
useEffect(() => {
if (config) {
setForm({
company_name: config.company_name,
autonomy_level: config.autonomy_level ?? 'semi',
budget_monthly: config.budget_monthly ?? 100,
communication_pattern: config.communication_pattern ?? 'hybrid',
})
}
}, [config])
return { form, setForm }
}
export function GeneralTab({ config, onUpdate, saving }: GeneralTabProps) {
const { form, setForm } = useCompanyForm(config)
const [submitError, setSubmitError] = useState<string | null>(null)
const handleSave = useCallback(async () => {
setSubmitError(null)
try {
await onUpdate({
company_name: form.company_name.trim() || undefined,
autonomy_level: form.autonomy_level as UpdateCompanyRequest['autonomy_level'],
budget_monthly: form.budget_monthly,
communication_pattern: form.communication_pattern.trim() || undefined,
})
} catch (err) {
setSubmitError(err instanceof Error ? err.message : 'Failed to save')
}
}, [form, onUpdate])
if (!config) {
return <EmptyState icon={Settings} title="No company data" description="Company configuration is not loaded yet." />
}
return (
<SectionCard title="Company Settings" icon={Settings}>
<div className="space-y-5 max-w-xl">
<InputField
label="Company Name"
value={form.company_name}
onChange={(e) => setForm((prev) => ({ ...prev, company_name: e.target.value }))}
required
/>
<SelectField
label="Autonomy Level"
options={AUTONOMY_OPTIONS}
value={form.autonomy_level}
onChange={(value) => setForm((prev) => ({ ...prev, autonomy_level: value }))}
/>
<SliderField
label="Monthly Budget"
value={form.budget_monthly}
onChange={(value) => setForm((prev) => ({ ...prev, budget_monthly: value }))}
min={0}
max={10000}
step={50}
formatValue={formatBudget}
/>
<InputField
label="Communication Pattern"
value={form.communication_pattern}
onChange={(e) => setForm((prev) => ({ ...prev, communication_pattern: e.target.value }))}
hint="e.g. hybrid, broadcast, hierarchical"
/>
{submitError && (
<p className="text-xs text-danger">{submitError}</p>
)}
<Button onClick={handleSave} disabled={saving}>
{saving && <Loader2 className="mr-2 size-4 animate-spin" />}
Save Settings
</Button>
</div>
</SectionCard>
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 35 - 117, GeneralTab is
>50 lines; extract the form UI and/or state into a smaller unit to comply with
the guideline: create a new CompanySettingsForm component (props: form, setForm,
submitError, saving, onSave) and move the JSX from the return block into it,
leaving GeneralTab to only initialize form state, run the useEffect that maps
config -> form, and pass handleSave (the existing callback) and state down;
alternatively extract form state and handleSave into a custom hook
(useCompanySettingsForm) that returns {form, setForm, submitError, saving,
handleSave} and keep GeneralTab as a thin wrapper that renders the new
CompanySettingsForm. Ensure you keep existing symbols (GeneralTab, handleSave,
useEffect, form, setForm, submitError) and adjust imports/props accordingly.

- Add disabled={saving} to second DepartmentCreateDialog instance
- Memoize deptOptions in AgentCreateDialog (matches AgentEditDrawer pattern)
- Use explicit Intl.NumberFormat locale in formatBudget for consistency
- Add Saving story for AgentsTab, budget_percent to DepartmentCreateDialog stub
- Test fetchDepartmentHealths sequential call after config loads
- Test DepartmentEditDrawer delete confirmation flow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 28, 2026 08:21 — 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: 10

♻️ Duplicate comments (1)
web/src/pages/org-edit/GeneralTab.tsx (1)

37-120: 🛠️ Refactor suggestion | 🟠 Major

Refactor GeneralTab to satisfy the 50-line function limit.

GeneralTab currently spans Line 37 through Line 120. Please extract form UI/state logic (e.g., CompanySettingsForm + useCompanySettingsForm) to keep each function under the guideline threshold.

Based on learnings: "Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines".

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 37 - 120, The GeneralTab
component exceeds the 50-line function limit; extract its form UI and state
logic into a new presentational component and hook (e.g., create
CompanySettingsForm and useCompanySettingsForm) and keep GeneralTab as a thin
wrapper that loads config and renders <CompanySettingsForm ...>. Move local
state initialization (form, submitError), the useEffect that syncs config into
form, and handleSave into useCompanySettingsForm (expose form, setForm,
submitError, handleSave), and move the JSX for inputs/controls into
CompanySettingsForm which receives props: form, setForm, submitError,
handleSave, saving. Update references to AUTONOMY_OPTIONS, formatBudget,
InputField, SelectField, SliderField, Button, Loader2, SectionCard and ensure
types UpdateCompanyRequest/FormState are used unchanged.
🤖 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/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 69-72: The test is using a module-scoped mock set with
mockImplementation() which persists behaviour across tests because
vi.clearAllMocks() only clears call history; change that specific module-scoped
mock call (the one currently using mockImplementation() in the test file for
useOrgEditData) to mockImplementationOnce() so the override applies only for
that single test run, preserving test isolation; keep the beforeEach() with
vi.clearAllMocks() and resetStore() as-is.

In `@web/src/pages/org-edit/AgentCreateDialog.tsx`:
- Around line 35-165: AgentCreateDialog is too large — extract the form state,
validation and submit logic into a custom hook (e.g., useAgentForm) and move the
JSX for the inner form fields into a presentational component (e.g.,
AgentCreateForm) so AgentCreateDialog only wires the Dialog and composes those
pieces. Specifically: create useAgentForm to own form (INITIAL_FORM), errors,
submitting, submitError, updateField, handleSubmit (which calls onCreate) and
deptOptions; create AgentCreateForm that receives form, errors, submitting,
submitError, updateField, handleSubmit and deptOptions and renders the
InputField/SelectField blocks plus buttons; then refactor AgentCreateDialog to
import and use useAgentForm and render <AgentCreateForm ... /> inside the
Dialog.Content, keeping AgentCreateDialog under ~50 lines.
- Around line 89-97: The Dialog in AgentCreateDialog.tsx is using hardcoded
Tailwind utilities (e.g., p-6, mb-4, space-y-4, gap-3, pt-2, mr-2, text-base,
text-xs, size-4) and hardcoded colors; update the Dialog.Overlay and
Dialog.Content blocks and any surrounding elements in AgentCreateDialog to use
the repo's semantic tokens/CSS variables instead (for example replace
bg-*/text-* with bg-card/text-foreground or var(--so-*) and replace spacing/font
utilities with p-card, gap-section-gap, space-section, text-sm/text-md semantic
tokens used across web/src). Locate usages inside the AgentCreateDialog
component (the Dialog.Overlay, Dialog.Content and form/group elements around
lines mentioned) and swap each literal utility to the corresponding semantic
class or CSS var to ensure consistent spacing, font sizing, and color tokens
across the file.
- Around line 55-79: The dialog currently allows overlay/Escape/X close while a
create request is in flight, causing handleSubmit's post-success
onOpenChange(false) and form reset to affect a newly reopened dialog; wrap the
Dialog.Root onOpenChange with a guard that returns early when submitting (use
the submitting state) so close attempts are ignored while setSubmitting is true,
and also disable the close X button (and any footer cancel button) by wiring
their disabled prop to submitting so the UI reflects this; update any direct
calls to onOpenChange(false) to use the guarded wrapper or check submitting
before calling to ensure no state is mutated after a close while the request was
pending.

In `@web/src/pages/org-edit/DepartmentsTab.tsx`:
- Around line 105-107: The code stores full Department objects in state
(editDept / setEditDept and similarly activeDept) which can become stale when
config.departments updates; change these states to store the department key
(e.g. department.name or id) instead of the object, and update all usages to
derive the current Department by looking it up from config.departments on each
render (replace direct object reads with a lookup like
config.departments.find(...) when you need the live object); update handlers
that open the drawer, save, and delete to set the key (via setEditDeptKey) and
to resolve the up-to-date Department from config before performing mutations;
apply the same change to the other occurrences referenced in the comment (the
edit/open handlers and setters at the other usages).
- Around line 72-85: This component reintroduces hardcoded Tailwind spacing and
breakpoint utilities (mt-2, space-y-4, space-y-6, mr-1.5, size-3.5,
max-[1023px]) inside DepartmentsTab/SectionCard/DeptHealthBar markup; replace
those raw classes with the project's semantic design tokens (e.g.,
gap-section-gap, p-card, size-* token classes, text-foreground/bg-card, and the
responsive token equivalents used elsewhere) so spacing and responsive behavior
follow the design system — update instances near the SectionCard and
DeptHealthBar usage (and the other occurrences around the commented ranges) to
use the same semantic spacing and responsive token classes used across the
codebase instead of hardcoded utilities.
- Around line 95-242: The DepartmentsTab function is too large and should be
split: extract the empty/create UI (the block that returns the EmptyState with
DepartmentCreateDialog and the top "Add Department" Button) into a small
DepartmentListEmpty component and extract the drag-and-drop grid plus
DragOverlay into a SortableDepartments component that encapsulates
DndContext/SortableContext, SortableDepartmentCard mapping, DragOverlay, and
uses the existing handlers (handleDragStart, handleDragEnd) and utilities
(getAgentCount, healthMap, optimisticReorderDepartments, onReorderDepartments);
keep DepartmentEditDrawer and DepartmentCreateDialog usage, preserve props
passed (onCreateDepartment, onUpdateDepartment, onDeleteDepartment, saving,
config, departmentHealths) and ensure the new components accept those props and
call back identically so behavior/optimistic rollback remains unchanged.

In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 46-55: The useEffect in GeneralTab.tsx currently overwrites the
local form state on every config change (useEffect -> setForm), which wipes
in-progress edits; add a dirty-guard/one-time hydrate: track whether the form is
dirty (e.g., isDirty state or a hydratedRef) and only call setForm with config
when the form is not dirty or when the record is first loaded (hydrate once),
and ensure after a successful save or explicit reset you clear the dirty flag
and resync from config so subsequent updates apply; update the effect dependency
and any save/reset handlers (the setForm caller, save handler, and reset
handler) to respect and toggle this dirty flag.
- Around line 24-29: The FormState interface and local form state use snake_case
identifiers; change them to camelCase (e.g., companyName, autonomyLevel,
budgetMonthly, communicationPattern) and update all local accessors/updates
(including the usages around the existing handlers at the region mentioned,
e.g., lines 61-64) to use the new camelCase names, then convert/map those
camelCase fields to the API's snake_case shape only when constructing the
UpdateCompanyRequest in the submit handler (assemble { company_name:
companyName, autonomy_level: autonomyLevel, budget_monthly: budgetMonthly,
communication_pattern: communicationPattern } before sending).
- Line 77: The container and inline utilities in GeneralTab.tsx are using
hardcoded Tailwind utilities (space-y-5, text-xs, mr-2 and icon prop size-4);
replace them with the project's semantic design tokens/CSS variables: swap
space-y-5 on the wrapper div for the gap token (e.g., gap-section-gap), replace
text-xs on the small/metadata text with the semantic text size/color class
(e.g., text-small or text-muted/text-foreground as appropriate), and replace
mr-2 and the Icon size-4 usage with the project spacing/size tokens (e.g.,
mr-interactive / size-sm or the CSS variable equivalents) so all spacing and
font-size values come from semantic classes/CSS variables rather than hardcoded
utilities. Ensure you update the className strings and Icon size prop in the
GeneralTab component accordingly.

---

Duplicate comments:
In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 37-120: The GeneralTab component exceeds the 50-line function
limit; extract its form UI and state logic into a new presentational component
and hook (e.g., create CompanySettingsForm and useCompanySettingsForm) and keep
GeneralTab as a thin wrapper that loads config and renders <CompanySettingsForm
...>. Move local state initialization (form, submitError), the useEffect that
syncs config into form, and handleSave into useCompanySettingsForm (expose form,
setForm, submitError, handleSave), and move the JSX for inputs/controls into
CompanySettingsForm which receives props: form, setForm, submitError,
handleSave, saving. Update references to AUTONOMY_OPTIONS, formatBudget,
InputField, SelectField, SliderField, Button, Loader2, SectionCard and ensure
types UpdateCompanyRequest/FormState are used unchanged.
🪄 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: 84f96dd1-2e83-4db6-ae97-3078f020a298

📥 Commits

Reviewing files that changed from the base of the PR and between aa3f3cd and a4c654a.

📒 Files selected for processing (7)
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
📜 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: Dashboard Test
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript, use Pydantic v2 models for Python
Always use async/await for promises; prefer asyncio.TaskGroup for parallel operations in Python
Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable names, function names, and identifiers

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Functions should be under 50 lines; files should be under 800 lines

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
  • web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentsTab.tsx
🧠 Learnings (6)
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/ui/** : Create a `.stories.tsx` file alongside every new UI component with all states (default, hover, loading, error, empty)

Applied to files:

  • web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx
  • web/src/pages/org-edit/AgentsTab.stories.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 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__/pages/org-edit/DepartmentEditDrawer.test.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx} : Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/org-edit/DepartmentsTab.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentCreateDialog.tsx

[warning] 45-45:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 44-44:
Do not call the 'set' function 'setErrors' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 43-43:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (9)
web/src/pages/org-edit/DepartmentCreateDialog.stories.tsx (3)

5-10: stubDept mock shape looks correct and realistic.

Nice update including budget_percent and a concrete Department-typed stub for stable Storybook behavior.


12-24: Story meta and default args are clean and type-safe.

onCreate now resolves a valid department mock, and the default open-state config is solid for interaction testing.


31-33: Closed state story is correctly scoped.

Good minimal override using args.open = false to exercise the non-visible state without duplicating config.

web/src/pages/org-edit/AgentsTab.stories.tsx (3)

89-90: Past review feedback addressed.

Good work on fixing the as never casts to return proper mock agents (mockAgents[0]!) and adding the Saving story variant. Both changes improve story reliability and coverage.

Also applies to: 106-108


5-78: Comprehensive mock data structure.

The mock agents cover diverse scenarios (different departments, levels, statuses, personality traits) which provides good story coverage. The nested structures align well with the AgentConfig type.


100-108: No Error story needed—AgentsTab does not accept error state as a prop.

The component manages errors internally through toast notifications on failed operations. The current stories (Default, Empty, Saving) cover all state variations exposed through the component's prop interface (config, saving).

			> Likely an incorrect or invalid review comment.
web/src/pages/org-edit/GeneralTab.tsx (1)

31-35: Good improvement on deterministic budget formatting.

Using explicit Intl.NumberFormat('en-US', ...) in formatBudget is a solid fix for cross-locale consistency.

web/src/__tests__/hooks/useOrgEditData.test.ts (1)

161-174: Strong hook-to-store wiring assertions here.

Using toBe for mutation function identity is exactly right for catching wiring regressions.

web/src/__tests__/pages/org-edit/DepartmentEditDrawer.test.tsx (1)

66-74: Nice destructive-flow coverage.

This closes the delete path by asserting the post-confirmation callback instead of only checking that the dialog opened.

Comment on lines +35 to +165
export function AgentCreateDialog({ open, onOpenChange, departments, onCreate }: AgentCreateDialogProps) {
const [form, setForm] = useState<FormState>(INITIAL_FORM)
const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({})
const [submitting, setSubmitting] = useState(false)
const [submitError, setSubmitError] = useState<string | null>(null)

useEffect(() => {
if (!open) {
setForm(INITIAL_FORM)
setErrors({})
setSubmitError(null)
}
}, [open])

function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
setForm((prev) => ({ ...prev, [key]: value }))
setErrors((prev) => ({ ...prev, [key]: undefined }))
setSubmitError(null)
}

const handleSubmit = useCallback(async () => {
const next: Partial<Record<keyof FormState, string>> = {}
if (!form.name.trim()) next.name = 'Name is required'
if (!form.role.trim()) next.role = 'Role is required'
if (!form.department) next.department = 'Department is required'
setErrors(next)
if (Object.keys(next).length > 0) return

setSubmitting(true)
setSubmitError(null)
try {
await onCreate({
name: form.name.trim(),
role: form.role.trim(),
department: form.department as CreateAgentOrgRequest['department'],
level: form.level,
})
setForm(INITIAL_FORM)
onOpenChange(false)
} catch (err) {
setSubmitError(getErrorMessage(err))
} finally {
setSubmitting(false)
}
}, [form, onCreate, onOpenChange])

const deptOptions = useMemo(
() => departments.map((d) => ({ value: d.name, label: d.display_name ?? d.name })),
[departments],
)

return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
<Dialog.Content
className={cn(
'fixed top-1/2 left-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2',
'rounded-xl border border-border-bright bg-surface p-6 shadow-lg',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
)}
>
<div className="flex items-center justify-between mb-4">
<Dialog.Title className="text-base font-semibold text-foreground">
New Agent
</Dialog.Title>
<Dialog.Close asChild>
<Button variant="ghost" size="icon" aria-label="Close">
<X className="size-4" />
</Button>
</Dialog.Close>
</div>

<div className="space-y-4">
<InputField
label="Name"
value={form.name}
onChange={(e) => updateField('name', e.target.value)}
error={errors.name}
required
autoFocus
placeholder="Agent name"
/>

<InputField
label="Role"
value={form.role}
onChange={(e) => updateField('role', e.target.value)}
error={errors.role}
required
placeholder="e.g. Backend Developer"
/>

<SelectField
label="Department"
options={deptOptions}
value={form.department}
onChange={(value) => updateField('department', value)}
error={errors.department}
required
placeholder="Select department..."
/>

<SelectField
label="Level"
options={LEVEL_OPTIONS}
value={form.level}
onChange={(value) => updateField('level', value as SeniorityLevel)}
/>

{submitError && (
<p className="text-xs text-danger">{submitError}</p>
)}

<div className="flex justify-end gap-3 pt-2">
<Dialog.Close asChild>
<Button variant="outline" disabled={submitting}>Cancel</Button>
</Dialog.Close>
<Button disabled={submitting} onClick={handleSubmit}>
{submitting && <Loader2 className="mr-2 size-4 animate-spin" />}
Create Agent
</Button>
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract the form logic and markup into smaller units.

AgentCreateDialog is already ~130 lines and mixes dialog wiring, field state, validation, async submission, and layout. Split the submit/validation state into a hook or helper and move at least one presentational section out so each function stays within the repo limit.

As per coding guidelines, Functions should be under 50 lines; files should be under 800 lines.

🧰 Tools
🪛 GitHub Check: Dashboard Lint

[warning] 45-45:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 44-44:
Do not call the 'set' function 'setErrors' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 43-43:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

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

In `@web/src/pages/org-edit/AgentCreateDialog.tsx` around lines 35 - 165,
AgentCreateDialog is too large — extract the form state, validation and submit
logic into a custom hook (e.g., useAgentForm) and move the JSX for the inner
form fields into a presentational component (e.g., AgentCreateForm) so
AgentCreateDialog only wires the Dialog and composes those pieces. Specifically:
create useAgentForm to own form (INITIAL_FORM), errors, submitting, submitError,
updateField, handleSubmit (which calls onCreate) and deptOptions; create
AgentCreateForm that receives form, errors, submitting, submitError,
updateField, handleSubmit and deptOptions and renders the InputField/SelectField
blocks plus buttons; then refactor AgentCreateDialog to import and use
useAgentForm and render <AgentCreateForm ... /> inside the Dialog.Content,
keeping AgentCreateDialog under ~50 lines.

Comment on lines +89 to +97
<Dialog.Overlay className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
<Dialog.Content
className={cn(
'fixed top-1/2 left-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2',
'rounded-xl border border-border-bright bg-surface p-6 shadow-lg',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace literal spacing/font utilities with semantic tokens.

This dialog hardcodes spacing/font sizing in several places (p-6, mb-4, space-y-4, gap-3, pt-2, mr-2, text-base, text-xs, size-4). Swap those for the repo’s semantic Tailwind tokens/CSS variables so web/src stays consistent.

As per coding guidelines, Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent)).

Also applies to: 99-100, 105-106, 110-110, 148-148, 151-156

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

In `@web/src/pages/org-edit/AgentCreateDialog.tsx` around lines 89 - 97, The
Dialog in AgentCreateDialog.tsx is using hardcoded Tailwind utilities (e.g.,
p-6, mb-4, space-y-4, gap-3, pt-2, mr-2, text-base, text-xs, size-4) and
hardcoded colors; update the Dialog.Overlay and Dialog.Content blocks and any
surrounding elements in AgentCreateDialog to use the repo's semantic tokens/CSS
variables instead (for example replace bg-*/text-* with bg-card/text-foreground
or var(--so-*) and replace spacing/font utilities with p-card, gap-section-gap,
space-section, text-sm/text-md semantic tokens used across web/src). Locate
usages inside the AgentCreateDialog component (the Dialog.Overlay,
Dialog.Content and form/group elements around lines mentioned) and swap each
literal utility to the corresponding semantic class or CSS var to ensure
consistent spacing, font sizing, and color tokens across the file.

Comment on lines +72 to +85
className="w-full text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent rounded-lg"
onClick={onClick}
onKeyDown={(e) => e.stopPropagation()}
aria-label={`Edit department ${dept.display_name ?? dept.name}`}
>
<SectionCard title={dept.display_name ?? dept.name} icon={Building2}>
<DeptHealthBar
name={dept.display_name ?? dept.name}
health={health?.health_percent}
agentCount={agentCount}
taskCount={health?.task_count}
/>
{dept.teams.length > 0 && (
<p className="mt-2 text-xs text-text-secondary">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace the raw spacing / breakpoint utilities with design tokens.

This file reintroduces hardcoded utilities like mt-2, space-y-4, space-y-6, mr-1.5, size-3.5, and max-[1023px]. Please swap those to the semantic spacing and responsive tokens used elsewhere so the tab stays aligned with the design system.

As per coding guidelines "Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files" and "Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))".

Also applies to: 159-165, 183-199

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

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 72 - 85, This
component reintroduces hardcoded Tailwind spacing and breakpoint utilities
(mt-2, space-y-4, space-y-6, mr-1.5, size-3.5, max-[1023px]) inside
DepartmentsTab/SectionCard/DeptHealthBar markup; replace those raw classes with
the project's semantic design tokens (e.g., gap-section-gap, p-card, size-*
token classes, text-foreground/bg-card, and the responsive token equivalents
used elsewhere) so spacing and responsive behavior follow the design system —
update instances near the SectionCard and DeptHealthBar usage (and the other
occurrences around the commented ranges) to use the same semantic spacing and
responsive token classes used across the codebase instead of hardcoded
utilities.

Comment on lines +95 to +242
export function DepartmentsTab({
config,
departmentHealths,
saving,
onCreateDepartment,
onUpdateDepartment,
onDeleteDepartment,
onReorderDepartments,
optimisticReorderDepartments,
}: DepartmentsTabProps) {
const [createOpen, setCreateOpen] = useState(false)
const [editDept, setEditDept] = useState<Department | null>(null)
const [activeDept, setActiveDept] = useState<Department | null>(null)

const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
)

const healthMap = useMemo(
() => new Map(departmentHealths.map((h) => [h.name, h])),
[departmentHealths],
)

const getAgentCount = useCallback(
(deptName: string): number => {
if (!config) return 0
return config.agents.filter((a) => a.department === deptName).length
},
[config],
)

const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveDept(event.active.data.current?.dept ?? null)
}, [])

const handleDragEnd = useCallback(
async (event: DragEndEvent) => {
setActiveDept(null)
const { active, over } = event
if (!over || active.id === over.id || !config) return

const oldIndex = config.departments.findIndex((d) => d.name === active.id)
const newIndex = config.departments.findIndex((d) => d.name === over.id)
if (oldIndex === -1 || newIndex === -1) return

const reordered = arrayMove([...config.departments], oldIndex, newIndex)
const orderedNames = reordered.map((d) => d.name)

const rollback = optimisticReorderDepartments(orderedNames)
try {
await onReorderDepartments(orderedNames)
} catch {
rollback()
useToastStore.getState().add({ variant: 'error', title: 'Failed to reorder departments' })
}
},
[config, optimisticReorderDepartments, onReorderDepartments],
)

const editHealth = editDept ? (healthMap.get(editDept.name) ?? null) : null

if (!config || config.departments.length === 0) {
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => setCreateOpen(true)} disabled={saving}>
<Plus className="mr-1.5 size-3.5" />
Add Department
</Button>
</div>
<EmptyState
icon={Building2}
title="No departments"
description="Create your first department to get started."
/>
<DepartmentCreateDialog
open={createOpen}
onOpenChange={setCreateOpen}
existingNames={[]}
onCreate={onCreateDepartment}
disabled={saving}
/>
</div>
)
}

return (
<div className="space-y-6">
<div className="flex justify-end">
<Button onClick={() => setCreateOpen(true)} disabled={saving}>
<Plus className="mr-1.5 size-3.5" />
Add Department
</Button>
</div>

<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={() => setActiveDept(null)}
>
<SortableContext items={config.departments.map((d) => d.name)} strategy={rectSortingStrategy}>
<StaggerGroup className="grid grid-cols-2 gap-grid-gap max-[1023px]:grid-cols-1">
{config.departments.map((dept) => (
<StaggerItem key={dept.name}>
<SortableDepartmentCard
dept={dept}
health={healthMap.get(dept.name) ?? null}
agentCount={getAgentCount(dept.name)}
onClick={() => setEditDept(dept)}
disabled={saving}
/>
</StaggerItem>
))}
</StaggerGroup>
</SortableContext>

<DragOverlay>
{activeDept && (
<div className="rounded-lg border border-accent bg-card p-card" style={{ boxShadow: 'var(--so-shadow-card-hover)' }}>
<p className="text-sm font-semibold text-foreground">{activeDept.display_name ?? activeDept.name}</p>
</div>
)}
</DragOverlay>
</DndContext>

<DepartmentCreateDialog
open={createOpen}
onOpenChange={setCreateOpen}
existingNames={config.departments.map((d) => d.name)}
onCreate={onCreateDepartment}
disabled={saving}
/>

<DepartmentEditDrawer
open={editDept !== null}
onClose={() => setEditDept(null)}
department={editDept}
health={editHealth}
onUpdate={onUpdateDepartment}
onDelete={onDeleteDepartment}
saving={saving}
/>
</div>
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Break DepartmentsTab up before it grows any further.

Lines 95-242 are already coordinating empty-state rendering, DnD setup, optimistic rollback, create flow, and the edit drawer in one function. That breaches the repo’s 50-line cap and makes the state transitions harder to audit. Extract at least the empty/create branch and the sortable grid/overlay into smaller components or hooks.

As per coding guidelines "Functions should be under 50 lines; files should be under 800 lines".

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

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 95 - 242, The
DepartmentsTab function is too large and should be split: extract the
empty/create UI (the block that returns the EmptyState with
DepartmentCreateDialog and the top "Add Department" Button) into a small
DepartmentListEmpty component and extract the drag-and-drop grid plus
DragOverlay into a SortableDepartments component that encapsulates
DndContext/SortableContext, SortableDepartmentCard mapping, DragOverlay, and
uses the existing handlers (handleDragStart, handleDragEnd) and utilities
(getAgentCount, healthMap, optimisticReorderDepartments, onReorderDepartments);
keep DepartmentEditDrawer and DepartmentCreateDialog usage, preserve props
passed (onCreateDepartment, onUpdateDepartment, onDeleteDepartment, saving,
config, departmentHealths) and ensure the new components accept those props and
call back identically so behavior/optimistic rollback remains unchanged.

Comment on lines +105 to +107
const [createOpen, setCreateOpen] = useState(false)
const [editDept, setEditDept] = useState<Department | null>(null)
const [activeDept, setActiveDept] = useState<Department | null>(null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Track the edited department by key, not by object snapshot.

Lines 105-107 keep the whole Department in editDept. This tab also consumes live polling/WebSocket updates from web/src/hooks/useOrgEditData.ts (Lines 44-132), so the drawer can stay bound to stale data while config.departments changes underneath it. Save/delete then keep operating on the old snapshot. Store only the department name in state and derive the current object from config each render.

💡 Minimal fix
-  const [editDept, setEditDept] = useState<Department | null>(null)
+  const [editDeptName, setEditDeptName] = useState<Department['name'] | null>(null)
+  const editDept = useMemo(
+    () => (editDeptName ? config?.departments.find((d) => d.name === editDeptName) ?? null : null),
+    [config, editDeptName],
+  )
   const [activeDept, setActiveDept] = useState<Department | null>(null)
...
-                  onClick={() => setEditDept(dept)}
+                  onClick={() => setEditDeptName(dept.name)}
...
-        onClose={() => setEditDept(null)}
+        onClose={() => setEditDeptName(null)}

Also applies to: 155-155, 206-207, 231-235

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

In `@web/src/pages/org-edit/DepartmentsTab.tsx` around lines 105 - 107, The code
stores full Department objects in state (editDept / setEditDept and similarly
activeDept) which can become stale when config.departments updates; change these
states to store the department key (e.g. department.name or id) instead of the
object, and update all usages to derive the current Department by looking it up
from config.departments on each render (replace direct object reads with a
lookup like config.departments.find(...) when you need the live object); update
handlers that open the drawer, save, and delete to set the key (via
setEditDeptKey) and to resolve the up-to-date Department from config before
performing mutations; apply the same change to the other occurrences referenced
in the comment (the edit/open handlers and setters at the other usages).

Comment on lines +24 to +29
interface FormState {
company_name: string
autonomy_level: string
budget_monthly: number
communication_pattern: string
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use camelCase for local form identifiers and map to API shape at submit boundary.

FormState and local state accessors use snake_case identifiers (company_name, autonomy_level, etc.). Keep local state camelCase, then map to snake_case only when constructing UpdateCompanyRequest.

As per coding guidelines: "**/*.{ts,tsx,js}: Use camelCase for variable names, function names, and identifiers".

Also applies to: 61-64

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 24 - 29, The FormState
interface and local form state use snake_case identifiers; change them to
camelCase (e.g., companyName, autonomyLevel, budgetMonthly,
communicationPattern) and update all local accessors/updates (including the
usages around the existing handlers at the region mentioned, e.g., lines 61-64)
to use the new camelCase names, then convert/map those camelCase fields to the
API's snake_case shape only when constructing the UpdateCompanyRequest in the
submit handler (assemble { company_name: companyName, autonomy_level:
autonomyLevel, budget_monthly: budgetMonthly, communication_pattern:
communicationPattern } before sending).


return (
<SectionCard title="Company Settings" icon={Settings}>
<div className="space-y-5 max-w-xl">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace hardcoded spacing/font utility classes with semantic design tokens.

Line 77 (space-y-5), Line 110 (text-xs), and Line 114 (mr-2, size-4) use hardcoded spacing/font-size utilities. Switch these to project semantic classes/CSS variables.

As per coding guidelines: "web/src/**/*.{ts,tsx}: Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))".

Also applies to: 110-110, 114-114

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

In `@web/src/pages/org-edit/GeneralTab.tsx` at line 77, The container and inline
utilities in GeneralTab.tsx are using hardcoded Tailwind utilities (space-y-5,
text-xs, mr-2 and icon prop size-4); replace them with the project's semantic
design tokens/CSS variables: swap space-y-5 on the wrapper div for the gap token
(e.g., gap-section-gap), replace text-xs on the small/metadata text with the
semantic text size/color class (e.g., text-small or text-muted/text-foreground
as appropriate), and replace mr-2 and the Icon size-4 usage with the project
spacing/size tokens (e.g., mr-interactive / size-sm or the CSS variable
equivalents) so all spacing and font-size values come from semantic classes/CSS
variables rather than hardcoded utilities. Ensure you update the className
strings and Icon size prop in the GeneralTab component accordingly.

- Use mockImplementationOnce for test isolation in useOrgEditData test
- Guard dialog close during submit in both create dialogs
- Add dirty guard to GeneralTab: WS/polling updates no longer wipe edits
- Clear dirty flag after successful save

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 28, 2026 08:36 — 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: 8

♻️ Duplicate comments (4)
web/src/pages/org-edit/GeneralTab.tsx (3)

37-128: 🛠️ Refactor suggestion | 🟠 Major

Split GeneralTab into smaller units to satisfy the function-length rule.

GeneralTab currently exceeds the 50-line limit (Line 37 onward). Please extract form rendering and/or form-state orchestration into a dedicated component/hook.

As per coding guidelines: "**/*.{py,ts,tsx,go}: Functions should be under 50 lines; files should be under 800 lines".

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 37 - 128, GeneralTab is
over the 50-line function limit; split form rendering/state management into
smaller units by extracting the form state logic (useState, useEffect,
updateForm, handleSave, submitError) into a custom hook (e.g. useGeneralForm)
and moving the JSX for the inputs into a child component (e.g.
GeneralFormFields) that accepts form, updateForm, submitError, saving, and
handleSave as props; keep GeneralTab as a thin wrapper that calls
useGeneralForm(config, onUpdate) and renders SectionCard with <GeneralFormFields
... /> so the original symbols (GeneralTab, updateForm, handleSave, form) are
preserved but the function body is under 50 lines.

24-29: 🛠️ Refactor suggestion | 🟠 Major

Use camelCase for local form state and map to snake_case only at submit.

Line 25 and related state/accessors still use snake_case identifiers locally. Keep component state in camelCase, then transform to API shape right before onUpdate to satisfy repo conventions and reduce mixed naming in UI logic.

♻️ Suggested refactor
-interface FormState {
-  company_name: string
-  autonomy_level: string
-  budget_monthly: number
-  communication_pattern: string
-}
+interface FormState {
+  companyName: string
+  autonomyLevel: string
+  budgetMonthly: number
+  communicationPattern: string
+}

-  const [form, setForm] = useState<FormState>({
-    company_name: '',
-    autonomy_level: 'semi',
-    budget_monthly: 100,
-    communication_pattern: 'hybrid',
-  })
+  const [form, setForm] = useState<FormState>({
+    companyName: '',
+    autonomyLevel: 'semi',
+    budgetMonthly: 100,
+    communicationPattern: 'hybrid',
+  })

-      await onUpdate({
-        company_name: form.company_name.trim() || undefined,
-        autonomy_level: form.autonomy_level as UpdateCompanyRequest['autonomy_level'],
-        budget_monthly: form.budget_monthly,
-        communication_pattern: form.communication_pattern.trim() || undefined,
-      })
+      await onUpdate({
+        company_name: form.companyName.trim() || undefined,
+        autonomy_level: form.autonomyLevel as UpdateCompanyRequest['autonomy_level'],
+        budget_monthly: form.budgetMonthly,
+        communication_pattern: form.communicationPattern.trim() || undefined,
+      })

As per coding guidelines: "**/*.{ts,tsx,js}: Use camelCase for variable names, function names, and identifiers".

Also applies to: 38-43, 68-71, 88-113

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 24 - 29, The FormState
interface and local state accessors should use camelCase (e.g., companyName,
autonomyLevel, budgetMonthly, communicationPattern) instead of snake_case;
update the FormState type and any useState/useRef/getters/setters in this
component (references around the FormState declaration and the accessors
mentioned at lines 38-43, 68-71, 88-113) to camelCase, and then convert to the
API shape (company_name, autonomy_level, budget_monthly, communication_pattern)
right before calling onUpdate/submit so the network payload uses snake_case
while UI logic stays camelCase.

85-85: 🛠️ Refactor suggestion | 🟠 Major

Replace hardcoded spacing/font utilities with semantic tokens.

Line 85 (space-y-5), Line 118 (text-xs), and Line 122 (mr-2, size-4) still use hardcoded utility sizing/typography. These should be swapped to approved semantic classes/CSS variables.

As per coding guidelines: "web/src/**/*.{ts,tsx}: Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes ... or CSS variables" and "**/*.{ts,tsx}: Use design tokens exclusively - no hardcoded ... pixel spacing values".

Also applies to: 118-118, 122-122

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

In `@web/src/pages/org-edit/GeneralTab.tsx` at line 85, The component GeneralTab
contains hardcoded Tailwind utilities (space-y-5, text-xs, mr-2 and size-4 on
the icon) that must be replaced with design tokens; update the container in
GeneralTab to use the approved spacing token class or CSS variable (replace
space-y-5 with the project semantic spacing token), swap text-xs for the
typography token class for the small body text, replace mr-2 with the semantic
margin token/CSS var, and change the icon's size-4 usage to the tokenized size
prop or CSS variable so all spacing and font sizes follow the project's design
tokens.
web/src/pages/org-edit/AgentCreateDialog.tsx (1)

35-165: 🛠️ Refactor suggestion | 🟠 Major

Split AgentCreateDialog into smaller units.

This component still owns dialog wiring, reset logic, validation, async submission, and all of the form markup, so it remains far above the repo’s 50-line function limit. Extract the state/submit flow into a hook or helper and move the body into a smaller presentational component.

Based on learnings, "Functions should be under 50 lines; files should be under 800 lines".

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

In `@web/src/pages/org-edit/AgentCreateDialog.tsx` around lines 35 - 165,
AgentCreateDialog is too large—split state/submit logic into a hook and keep the
JSX as a small presentational component: extract form state, validation and
async submit into a new hook (e.g., useAgentCreateForm) that owns INITIAL_FORM,
form state, errors, updateField, handleSubmit, submitting, submitError, and
reset logic (the useEffect that clears on open), and export deptOptions
computation into the hook or a small selector; then refactor AgentCreateDialog
to only render the dialog markup and pass props/callbacks from
useAgentCreateForm (name, role, department, level, onChange handlers, errors,
submitting, submitError, onSubmit, onCancel). Ensure unique identifiers remain
consistent (INITIAL_FORM, updateField, handleSubmit, useAgentCreateForm,
AgentCreateDialog) and move async onCreate usage into the hook so the component
stays under 50 lines.
🤖 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/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 43-66: The resetStore helper does not reset the healthError field,
causing state leakage between tests; update the resetStore implementation that
calls useCompanyStore.setState to include healthError: null (matching the
CompanyState type) alongside the other initial fields so tests start with a
clean store state.

In `@web/src/pages/org-edit/AgentCreateDialog.tsx`:
- Around line 55-70: handleSubmit currently only checks truthiness of
form.department and force-casts it to CreateAgentOrgRequest['department'] which
allows stale/removed departments to be submitted; before calling onCreate,
verify that form.department exists in the current departments list (e.g. find by
id or value against the departments prop/state) and if not set an appropriate
error via setErrors (e.g. next.department = 'Selected department is no longer
available') and return, then proceed to setSubmitting and call onCreate; apply
the same membership validation logic for the similar submission path around the
code referenced at lines 81-84 so both create and update flows reject stale
departments.
- Around line 55-79: handleSubmit can be re-entered on a fast double-click
because setSubmitting only updates after render; add a first-line re-entrancy
guard (e.g., create a submittingRef via useRef(false)) and at the top of
handleSubmit return early if submittingRef.current is true, then immediately set
submittingRef.current = true and call setSubmitting(true); in the finally block
set submittingRef.current = false (and setSubmitting(false)) so onCreate can't
be invoked twice; reference handleSubmit, submitting, setSubmitting, and
onCreate when making the change.

In `@web/src/pages/org-edit/DepartmentCreateDialog.tsx`:
- Around line 30-145: DepartmentCreateDialog is too long and mixes concerns;
extract the validation/reset logic into a helper validateAndNormalizeForm (or
validateForm) that takes FormState and existingNames and returns the errors
object and normalized payload, and extract the JSX form UI into a separate
DepartmentForm component that receives props: form, errors, submitting,
disabled, updateField, submitError and onCancel; in DepartmentCreateDialog keep
only state hooks, onCreate orchestration (use handleSubmit to call
validateAndNormalizeForm, setErrors, setSubmitting, call onCreate, reset to
INITIAL_FORM on success), and render <DepartmentForm ... />; reference existing
symbols: DepartmentCreateDialog, handleSubmit, updateField, INITIAL_FORM,
InputField to locate code to split.
- Around line 80-85: The dialog uses hardcoded spacing utility classes (e.g.,
'p-6', 'mb-4', 'space-y-4', 'gap-3', 'pt-2') inside the DepartmentCreateDialog
JSX className strings; replace those with the project's semantic spacing
tokens/CSS variables (for example use p-card instead of p-6, mb-<semantic>
instead of mb-4, space-y-<semantic> instead of space-y-4, gap-section-gap
instead of gap-3, pt-<semantic> or p-form-top instead of pt-2) so spacing
follows the tokenized system; update every occurrence in DepartmentCreateDialog
(including the other instances called out around the same block) to use the
project's semantic Tailwind classes or CSS variables (e.g., p-card, bg-card,
gap-section-gap) rather than hardcoded numeric spacing.
- Around line 18-22: FormState and related local state/error keys use
snake_case; change them to camelCase (e.g., displayName, budgetPercent)
throughout the component (FormState, any useState calls, validation/error
objects and prop names) and update all references inside DepartmentCreateDialog
accordingly, then convert to the API's snake_case only when building the payload
in onCreate (map displayName -> display_name, budgetPercent -> budget_percent)
so the internal TS identifiers follow camelCase while the network boundary
performs the snake_case translation.
- Around line 75-76: The dialog's onOpenChange handler (attached to Dialog.Root)
only toggles visibility and can leave local form values and error state intact;
update the onOpenChange callback so that when v === false (dialog closed by user
and not submitting) it also clears the component's local form state and
validation errors — e.g., call whatever reset function you have
(resetForm/reset, setFormValues to defaults, clearErrors/setErrors({})) or a
dedicated handleClose that resets state before calling onOpenChange; keep the
existing submitting guard so automatic resets don't run while submitting.

In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 26-27: Change the FormState type so autonomy_level is typed as the
AutonomyLevel union (not string), and add validation/coercion when
hydrating/reading config and before submit: implement a small
validateAutonomyLevel(value: unknown): AutonomyLevel | undefined used in the
form initialization (e.g., wherever you map config -> FormState such as
initializeForm/hydrateFormFromConfig) and in the submit handler
(onSubmit/handleSubmit) to avoid unsafe casts; ensure invalid values are
rejected or defaulted and update usages that previously cast autonomy_level as
string to rely on the validated AutonomyLevel.

---

Duplicate comments:
In `@web/src/pages/org-edit/AgentCreateDialog.tsx`:
- Around line 35-165: AgentCreateDialog is too large—split state/submit logic
into a hook and keep the JSX as a small presentational component: extract form
state, validation and async submit into a new hook (e.g., useAgentCreateForm)
that owns INITIAL_FORM, form state, errors, updateField, handleSubmit,
submitting, submitError, and reset logic (the useEffect that clears on open),
and export deptOptions computation into the hook or a small selector; then
refactor AgentCreateDialog to only render the dialog markup and pass
props/callbacks from useAgentCreateForm (name, role, department, level, onChange
handlers, errors, submitting, submitError, onSubmit, onCancel). Ensure unique
identifiers remain consistent (INITIAL_FORM, updateField, handleSubmit,
useAgentCreateForm, AgentCreateDialog) and move async onCreate usage into the
hook so the component stays under 50 lines.

In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 37-128: GeneralTab is over the 50-line function limit; split form
rendering/state management into smaller units by extracting the form state logic
(useState, useEffect, updateForm, handleSave, submitError) into a custom hook
(e.g. useGeneralForm) and moving the JSX for the inputs into a child component
(e.g. GeneralFormFields) that accepts form, updateForm, submitError, saving, and
handleSave as props; keep GeneralTab as a thin wrapper that calls
useGeneralForm(config, onUpdate) and renders SectionCard with <GeneralFormFields
... /> so the original symbols (GeneralTab, updateForm, handleSave, form) are
preserved but the function body is under 50 lines.
- Around line 24-29: The FormState interface and local state accessors should
use camelCase (e.g., companyName, autonomyLevel, budgetMonthly,
communicationPattern) instead of snake_case; update the FormState type and any
useState/useRef/getters/setters in this component (references around the
FormState declaration and the accessors mentioned at lines 38-43, 68-71, 88-113)
to camelCase, and then convert to the API shape (company_name, autonomy_level,
budget_monthly, communication_pattern) right before calling onUpdate/submit so
the network payload uses snake_case while UI logic stays camelCase.
- Line 85: The component GeneralTab contains hardcoded Tailwind utilities
(space-y-5, text-xs, mr-2 and size-4 on the icon) that must be replaced with
design tokens; update the container in GeneralTab to use the approved spacing
token class or CSS variable (replace space-y-5 with the project semantic spacing
token), swap text-xs for the typography token class for the small body text,
replace mr-2 with the semantic margin token/CSS var, and change the icon's
size-4 usage to the tokenized size prop or CSS variable so all spacing and font
sizes follow the project's design tokens.
🪄 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: 9d6d166b-37fb-41d4-9850-a523532b1dac

📥 Commits

Reviewing files that changed from the base of the PR and between a4c654a and f8b5d20.

📒 Files selected for processing (4)
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
📜 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). (3)
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript, use Pydantic v2 models for Python
Always use async/await for promises; prefer asyncio.TaskGroup for parallel operations in Python
Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable names, function names, and identifiers

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Functions should be under 50 lines; files should be under 800 lines

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/AgentCreateDialog.tsx
🧠 Learnings (7)
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx,js} : Use camelCase for variable names, function names, and identifiers

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx} : Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/AgentCreateDialog.tsx
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to web/**/*.vue : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in dashboard code — use generic names: `example-provider`, `example-large-001`, etc.

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 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__/hooks/useOrgEditData.test.ts
🪛 GitHub Check: Dashboard Lint
web/src/pages/org-edit/AgentCreateDialog.tsx

[warning] 45-45:
Do not call the 'set' function 'setSubmitError' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 44-44:
Do not call the 'set' function 'setErrors' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues


[warning] 43-43:
Do not call the 'set' function 'setForm' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues

🔇 Additional comments (10)
web/src/__tests__/hooks/useOrgEditData.test.ts (10)

1-5: LGTM!

Imports are clean and follow proper naming conventions.


6-19: LGTM!

Mock function setup follows Vitest best practices with appropriate return value patterns for async and sync functions.


21-24: LGTM!

Correct use of vi.hoisted() to enable mock references in the module factory.


26-41: LGTM!

Mock setup for useWebSocket and usePolling correctly provides default return values and enables test-specific overrides.


68-72: LGTM!

Proper test isolation with mock cleanup and store reset in beforeEach.


74-80: LGTM!

Test correctly verifies initialization behavior: data fetch on mount and polling start. Properly addresses the polling lifecycle testing from past reviews.


82-90: LGTM!

Excellent test of sequential fetch behavior using mockImplementationOnce to prevent mock pollution. Properly addresses past review feedback.


92-99: LGTM!

Polling lifecycle coverage is complete with start and stop verification. Properly addresses past review feedback.


101-137: LGTM!

Comprehensive state passthrough testing. The saving/saveError test correctly validates the derived state logic (saving computed from savingCount > 0).


139-174: LGTM!

Both test segments demonstrate excellent practices:

  • wsSetupError test uses afterEach for guaranteed mock restoration (addresses past review concern)
  • Mutation function test uses toBe() to verify function identity rather than just existence (addresses past review concern)
  • Complete coverage of all 11 mutation/optimistic functions from the store interface

Comment on lines +55 to +70
const handleSubmit = useCallback(async () => {
const next: Partial<Record<keyof FormState, string>> = {}
if (!form.name.trim()) next.name = 'Name is required'
if (!form.role.trim()) next.role = 'Role is required'
if (!form.department) next.department = 'Department is required'
setErrors(next)
if (Object.keys(next).length > 0) return

setSubmitting(true)
setSubmitError(null)
try {
await onCreate({
name: form.name.trim(),
role: form.role.trim(),
department: form.department as CreateAgentOrgRequest['department'],
level: form.level,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject stale departments before sending the mutation.

CreateAgentOrgRequest.department is not an arbitrary string, but the current validation only checks truthiness and Line 69 force-casts whatever is in state. If departments changes while the dialog is open, a removed department still passes validation and gets submitted. Validate membership against departments before calling onCreate.

🧪 Suggested validation
   const handleSubmit = useCallback(async () => {
     if (submitting) return

     const next: Partial<Record<keyof FormState, string>> = {}
     if (!form.name.trim()) next.name = 'Name is required'
     if (!form.role.trim()) next.role = 'Role is required'
     if (!form.department) next.department = 'Department is required'
+    else if (!departments.some((department) => department.name === form.department)) {
+      next.department = 'Department is no longer available'
+    }
     setErrors(next)
     if (Object.keys(next).length > 0) return
@@
-  }, [form, onCreate, onOpenChange, submitting])
+  }, [departments, form, onCreate, onOpenChange, submitting])

Also applies to: 81-84

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

In `@web/src/pages/org-edit/AgentCreateDialog.tsx` around lines 55 - 70,
handleSubmit currently only checks truthiness of form.department and force-casts
it to CreateAgentOrgRequest['department'] which allows stale/removed departments
to be submitted; before calling onCreate, verify that form.department exists in
the current departments list (e.g. find by id or value against the departments
prop/state) and if not set an appropriate error via setErrors (e.g.
next.department = 'Selected department is no longer available') and return, then
proceed to setSubmitting and call onCreate; apply the same membership validation
logic for the similar submission path around the code referenced at lines 81-84
so both create and update flows reject stale departments.

Comment on lines +55 to +79
const handleSubmit = useCallback(async () => {
const next: Partial<Record<keyof FormState, string>> = {}
if (!form.name.trim()) next.name = 'Name is required'
if (!form.role.trim()) next.role = 'Role is required'
if (!form.department) next.department = 'Department is required'
setErrors(next)
if (Object.keys(next).length > 0) return

setSubmitting(true)
setSubmitError(null)
try {
await onCreate({
name: form.name.trim(),
role: form.role.trim(),
department: form.department as CreateAgentOrgRequest['department'],
level: form.level,
})
setForm(INITIAL_FORM)
onOpenChange(false)
} catch (err) {
setSubmitError(getErrorMessage(err))
} finally {
setSubmitting(false)
}
}, [form, onCreate, onOpenChange])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent duplicate create submissions.

submitting only disables the button after React commits the next render. A fast double-click can enter handleSubmit twice and fire onCreate twice before that happens. Add a first-line re-entrancy guard here.

🔒 Suggested guard
   const handleSubmit = useCallback(async () => {
+    if (submitting) return
+
     const next: Partial<Record<keyof FormState, string>> = {}
     if (!form.name.trim()) next.name = 'Name is required'
     if (!form.role.trim()) next.role = 'Role is required'
     if (!form.department) next.department = 'Department is required'
@@
-  }, [form, onCreate, onOpenChange])
+  }, [form, onCreate, onOpenChange, submitting])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/AgentCreateDialog.tsx` around lines 55 - 79,
handleSubmit can be re-entered on a fast double-click because setSubmitting only
updates after render; add a first-line re-entrancy guard (e.g., create a
submittingRef via useRef(false)) and at the top of handleSubmit return early if
submittingRef.current is true, then immediately set submittingRef.current = true
and call setSubmitting(true); in the finally block set submittingRef.current =
false (and setSubmitting(false)) so onCreate can't be invoked twice; reference
handleSubmit, submitting, setSubmitting, and onCreate when making the change.

Comment on lines +18 to +22
interface FormState {
name: string
display_name: string
budget_percent: string
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use camelCase for local TypeScript identifiers; map to API snake_case only at boundary.

Local state and error keys (display_name, budget_percent) violate the naming rule for TS/TSX identifiers. Keep internal state camelCase and convert only in the onCreate payload.

♻️ Proposed refactor
 interface FormState {
   name: string
-  display_name: string
-  budget_percent: string
+  displayName: string
+  budgetPercent: string
 }

 const INITIAL_FORM: FormState = {
   name: '',
-  display_name: '',
-  budget_percent: '0',
+  displayName: '',
+  budgetPercent: '0',
 }

- if (!form.display_name.trim()) next.display_name = 'Display name is required'
- const pct = Number(form.budget_percent)
+ if (!form.displayName.trim()) next.displayName = 'Display name is required'
+ const pct = Number(form.budgetPercent)

 await onCreate({
   name: form.name.trim(),
-  display_name: form.display_name.trim(),
-  budget_percent: Number(form.budget_percent),
+  display_name: form.displayName.trim(),
+  budget_percent: Number(form.budgetPercent),
 })

 <InputField
   label="Display Name"
-  value={form.display_name}
-  onChange={(e) => updateField('display_name', e.target.value)}
-  error={errors.display_name}
+  value={form.displayName}
+  onChange={(e) => updateField('displayName', e.target.value)}
+  error={errors.displayName}
 />

 <InputField
   label="Budget %"
   type="number"
-  value={form.budget_percent}
-  onChange={(e) => updateField('budget_percent', e.target.value)}
-  error={errors.budget_percent}
+  value={form.budgetPercent}
+  onChange={(e) => updateField('budgetPercent', e.target.value)}
+  error={errors.budgetPercent}
 />

As per coding guidelines, Use camelCase for variable names, function names, and identifiers.

Also applies to: 24-28, 49-53, 61-63, 109-123

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

In `@web/src/pages/org-edit/DepartmentCreateDialog.tsx` around lines 18 - 22,
FormState and related local state/error keys use snake_case; change them to
camelCase (e.g., displayName, budgetPercent) throughout the component
(FormState, any useState calls, validation/error objects and prop names) and
update all references inside DepartmentCreateDialog accordingly, then convert to
the API's snake_case only when building the payload in onCreate (map displayName
-> display_name, budgetPercent -> budget_percent) so the internal TS identifiers
follow camelCase while the network boundary performs the snake_case translation.

Comment on lines +30 to +145
export function DepartmentCreateDialog({ open, onOpenChange, existingNames, onCreate, disabled }: DepartmentCreateDialogProps) {
const [form, setForm] = useState<FormState>(INITIAL_FORM)
const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({})
const [submitting, setSubmitting] = useState(false)
const [submitError, setSubmitError] = useState<string | null>(null)

function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
setForm((prev) => ({ ...prev, [key]: value }))
setErrors((prev) => ({ ...prev, [key]: undefined }))
setSubmitError(null)
}

const handleSubmit = useCallback(async () => {
const next: Partial<Record<keyof FormState, string>> = {}
if (!form.name.trim()) {
next.name = 'Name is required'
} else if (existingNames.some((n) => n.toLowerCase() === form.name.trim().toLowerCase())) {
next.name = 'Department already exists'
}
if (!form.display_name.trim()) next.display_name = 'Display name is required'
const pct = Number(form.budget_percent)
if (!Number.isFinite(pct) || pct < 0 || pct > 100) {
next.budget_percent = 'Must be between 0 and 100'
}
setErrors(next)
if (Object.keys(next).length > 0) return

setSubmitting(true)
setSubmitError(null)
try {
await onCreate({
name: form.name.trim(),
display_name: form.display_name.trim(),
budget_percent: Number(form.budget_percent),
})
setForm(INITIAL_FORM)
onOpenChange(false)
} catch (err) {
setSubmitError(getErrorMessage(err))
} finally {
setSubmitting(false)
}
}, [form, existingNames, onCreate, onOpenChange])

return (
<Dialog.Root open={open} onOpenChange={(v) => { if (!submitting) onOpenChange(v) }}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
<Dialog.Content
className={cn(
'fixed top-1/2 left-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2',
'rounded-xl border border-border-bright bg-surface p-6 shadow-lg',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
)}
>
<div className="flex items-center justify-between mb-4">
<Dialog.Title className="text-base font-semibold text-foreground">
New Department
</Dialog.Title>
<Dialog.Close asChild>
<Button variant="ghost" size="icon" aria-label="Close">
<X className="size-4" />
</Button>
</Dialog.Close>
</div>

<div className="space-y-4">
<InputField
label="Name"
value={form.name}
onChange={(e) => updateField('name', e.target.value)}
error={errors.name}
required
autoFocus
placeholder="e.g. engineering"
/>

<InputField
label="Display Name"
value={form.display_name}
onChange={(e) => updateField('display_name', e.target.value)}
error={errors.display_name}
required
placeholder="e.g. Engineering"
/>

<InputField
label="Budget %"
type="number"
value={form.budget_percent}
onChange={(e) => updateField('budget_percent', e.target.value)}
error={errors.budget_percent}
hint="Percentage of company budget (0-100)"
/>

{submitError && (
<p className="text-xs text-danger">{submitError}</p>
)}

<div className="flex justify-end gap-3 pt-2">
<Dialog.Close asChild>
<Button variant="outline" disabled={submitting}>Cancel</Button>
</Dialog.Close>
<Button disabled={submitting || disabled} onClick={handleSubmit}>
{submitting && <Loader2 className="mr-2 size-4 animate-spin" />}
Create Department
</Button>
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Split DepartmentCreateDialog into smaller units (function-length rule violation).

DepartmentCreateDialog is currently 100+ lines (starting at Line 30), which exceeds the repository limit and is already mixing rendering, validation, and submit orchestration in one block. Please extract at least validation/state-reset and form body into helpers/components.

As per coding guidelines, Functions should be under 50 lines; files should be under 800 lines.

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

In `@web/src/pages/org-edit/DepartmentCreateDialog.tsx` around lines 30 - 145,
DepartmentCreateDialog is too long and mixes concerns; extract the
validation/reset logic into a helper validateAndNormalizeForm (or validateForm)
that takes FormState and existingNames and returns the errors object and
normalized payload, and extract the JSX form UI into a separate DepartmentForm
component that receives props: form, errors, submitting, disabled, updateField,
submitError and onCancel; in DepartmentCreateDialog keep only state hooks,
onCreate orchestration (use handleSubmit to call validateAndNormalizeForm,
setErrors, setSubmitting, call onCreate, reset to INITIAL_FORM on success), and
render <DepartmentForm ... />; reference existing symbols:
DepartmentCreateDialog, handleSubmit, updateField, INITIAL_FORM, InputField to
locate code to split.

Comment on lines +75 to +76
<Dialog.Root open={open} onOpenChange={(v) => { if (!submitting) onOpenChange(v) }}>
<Dialog.Portal>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reset form/error state when the dialog is closed manually.

On Line 75, closing the dialog only toggles visibility. If the user cancels and reopens, stale values/errors can persist. Clear local state on close (when not submitting) to avoid carrying old input into a fresh create flow.

✅ Focused fix
+ const resetState = useCallback(() => {
+   setForm(INITIAL_FORM)
+   setErrors({})
+   setSubmitError(null)
+ }, [])
+
+ const handleOpenChange = useCallback((nextOpen: boolean) => {
+   if (submitting) return
+   if (!nextOpen) resetState()
+   onOpenChange(nextOpen)
+ }, [onOpenChange, resetState, submitting])

- <Dialog.Root open={open} onOpenChange={(v) => { if (!submitting) onOpenChange(v) }}>
+ <Dialog.Root open={open} onOpenChange={handleOpenChange}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/org-edit/DepartmentCreateDialog.tsx` around lines 75 - 76, The
dialog's onOpenChange handler (attached to Dialog.Root) only toggles visibility
and can leave local form values and error state intact; update the onOpenChange
callback so that when v === false (dialog closed by user and not submitting) it
also clears the component's local form state and validation errors — e.g., call
whatever reset function you have (resetForm/reset, setFormValues to defaults,
clearErrors/setErrors({})) or a dedicated handleClose that resets state before
calling onOpenChange; keep the existing submitting guard so automatic resets
don't run while submitting.

Comment on lines +80 to +85
'fixed top-1/2 left-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2',
'rounded-xl border border-border-bright bg-surface p-6 shadow-lg',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace hardcoded spacing scale classes with semantic spacing tokens/CSS variables.

This file uses hardcoded spacing classes (p-6, mb-4, space-y-4, gap-3, pt-2). In web/src, spacing must be semantic/tokenized.

As per coding guidelines, Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent)).

Also applies to: 87-88, 98-99, 131-137

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

In `@web/src/pages/org-edit/DepartmentCreateDialog.tsx` around lines 80 - 85, The
dialog uses hardcoded spacing utility classes (e.g., 'p-6', 'mb-4', 'space-y-4',
'gap-3', 'pt-2') inside the DepartmentCreateDialog JSX className strings;
replace those with the project's semantic spacing tokens/CSS variables (for
example use p-card instead of p-6, mb-<semantic> instead of mb-4,
space-y-<semantic> instead of space-y-4, gap-section-gap instead of gap-3,
pt-<semantic> or p-form-top instead of pt-2) so spacing follows the tokenized
system; update every occurrence in DepartmentCreateDialog (including the other
instances called out around the same block) to use the project's semantic
Tailwind classes or CSS variables (e.g., p-card, bg-card, gap-section-gap)
rather than hardcoded numeric spacing.

- Add healthError: null to resetStore in useOrgEditData test
- Reset DepartmentCreateDialog form state on close (matches AgentCreateDialog)
- Type autonomy_level as AutonomyLevel union instead of string, remove unsafe cast
- Validate autonomy_level value before updating form state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 28, 2026 08:52 — 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

♻️ Duplicate comments (1)
web/src/pages/org-edit/GeneralTab.tsx (1)

51-56: 🧹 Nitpick | 🔵 Trivial

Consider adding validation when hydrating autonomy_level from config.

The fix at line 100 validates user input before updating autonomy_level, but line 53 directly assigns config.autonomy_level ?? 'semi' without validation. If the API ever returns an unexpected value, it would bypass the type safety you've established.

♻️ Consistent validation approach
+function isValidAutonomyLevel(value: unknown): value is AutonomyLevel {
+  return typeof value === 'string' && VALID_AUTONOMY_LEVELS.has(value)
+}
+
 useEffect(() => {
   if (config && !dirty) {
     setForm({
       company_name: config.company_name,
-      autonomy_level: config.autonomy_level ?? 'semi',
+      autonomy_level: isValidAutonomyLevel(config.autonomy_level) ? config.autonomy_level : 'semi',
       budget_monthly: config.budget_monthly ?? 100,
       communication_pattern: config.communication_pattern ?? 'hybrid',
     })
   }
 }, [config])

This also allows reuse in the onChange handler at line 100.

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 51 - 56, When hydrating
state via setForm ensure autonomy_level is validated instead of blindly using
config.autonomy_level; add or reuse the same validation used in the onChange
handler (the autonomy_level validation logic) to check config.autonomy_level and
fall back to 'semi' if invalid, then pass the validated value into setForm
(referencing setForm, config.autonomy_level, and the onChange handler's
validation routine).
🤖 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/__tests__/hooks/useOrgEditData.test.ts`:
- Around line 26-32: Extract the repeated mock payload for the useWebSocket mock
into a shared constant (e.g., WEB_SOCKET_MOCK or webSocketMockPayload) and
replace inline object literals used in vi.mock(...) and other test mocks in
useOrgEditData.test.ts (locations around the useWebSocket mock and the blocks
referenced at lines ~141-147 and ~152-156) with that constant; update any
imports or test-scoped declarations so the constant is visible to all mocks in
the file and ensure all existing mocks (useWebSocket) return that shared
constant to avoid duplication and drift.

---

Duplicate comments:
In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 51-56: When hydrating state via setForm ensure autonomy_level is
validated instead of blindly using config.autonomy_level; add or reuse the same
validation used in the onChange handler (the autonomy_level validation logic) to
check config.autonomy_level and fall back to 'semi' if invalid, then pass the
validated value into setForm (referencing setForm, config.autonomy_level, and
the onChange handler's validation routine).
🪄 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: 8fb81145-6012-4955-a8aa-afd98510cbcb

📥 Commits

Reviewing files that changed from the base of the PR and between f8b5d20 and 8057b7d.

📒 Files selected for processing (3)
  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
  • web/src/pages/org-edit/GeneralTab.tsx
📜 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). (4)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript, use Pydantic v2 models for Python
Always use async/await for promises; prefer asyncio.TaskGroup for parallel operations in Python
Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable names, function names, and identifiers

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Functions should be under 50 lines; files should be under 800 lines

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Files:

  • web/src/__tests__/hooks/useOrgEditData.test.ts
  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
🧠 Learnings (7)
📚 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__/hooks/useOrgEditData.test.ts
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx,js} : Use camelCase for variable names, function names, and identifiers

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx} : Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
  • web/src/pages/org-edit/DepartmentCreateDialog.tsx
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to web/**/*.vue : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in dashboard code — use generic names: `example-provider`, `example-large-001`, etc.

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
🔇 Additional comments (15)
web/src/__tests__/hooks/useOrgEditData.test.ts (3)

43-67: Store reset coverage is complete and isolation-friendly.

resetStore() now resets all key fields (including healthError) and rewires every action used by the hook, which keeps test state deterministic between cases.


75-100: Lifecycle assertions are strong for side-effect wiring.

The suite now verifies mount fetch + polling start and unmount polling stop, which gives good protection against regressions in hook effects.


162-175: Great choice to assert function identity, not just function type.

Using toBe(...) for mutation handlers correctly validates wiring to the exact store actions.

web/src/pages/org-edit/DepartmentCreateDialog.tsx (5)

18-28: Local state keys still use snake_case.

The FormState interface and INITIAL_FORM still use display_name and budget_percent instead of camelCase (displayName, budgetPercent). This was flagged in a previous review round and remains unaddressed.


30-153: Component still exceeds the 50-line function limit.

DepartmentCreateDialog remains ~123 lines, mixing validation, state management, and rendering. The prior suggestion to extract validation logic and form body into smaller units has not been applied.


89-89: Hardcoded spacing classes remain unaddressed.

The component still uses numeric spacing utilities (p-6, mb-4, space-y-4, gap-3, pt-2, mr-2) instead of semantic tokens as required by the project guidelines.

Also applies to: 95-95, 106-106, 139-139, 144-144


36-42: Good: Form state resets when the dialog closes.

The useEffect properly clears form values, validation errors, and submit errors when open transitions to false. This prevents stale data from persisting across dialog sessions.


2-2: The import { Dialog } from 'radix-ui' is correct. The project uses the unified radix-ui v1.4.3 package, which exports components directly. This pattern is consistent across the codebase (AlertDialog, Tabs, Slot, etc. all import from 'radix-ui'), and there are no scoped @radix-ui/* packages in the project dependencies.

			> Likely an incorrect or invalid review comment.
web/src/pages/org-edit/GeneralTab.tsx (7)

26-31: FormState still uses snake_case identifiers.

The past review flagged this: local state should use camelCase (companyName, autonomyLevel, budgetMonthly, communicationPattern) and map to snake_case only when constructing the UpdateCompanyRequest payload. This keeps internal code consistent with JS/TS conventions.


39-79: Function still exceeds 50-line guideline.

GeneralTab spans ~93 lines. The previous suggestion to extract form state logic into a custom hook (useCompanyForm) or the form JSX into a separate component remains applicable.

As per coding guidelines: "Functions should be under 50 lines".


87-87: Hardcoded spacing and font-size utilities remain.

The following still use hardcoded Tailwind utilities instead of semantic design tokens:

  • Line 87: space-y-5
  • Line 122: text-xs
  • Line 126: mr-2 size-4

As per coding guidelines: "Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables".

Also applies to: 122-122, 126-126


33-37: Locale-aware formatting correctly implemented.

Good fix—using Intl.NumberFormat with an explicit 'en-US' locale ensures consistent number formatting across browsers.


49-59: Dirty guard correctly implemented.

The effect now properly checks !dirty before hydrating form state from config, preventing in-progress edits from being overwritten by polling/WebSocket updates.


95-102: Good: Input validation before state update.

The check VALID_AUTONOMY_LEVELS.has(value) before calling updateForm ensures only valid autonomy levels are accepted from user input, making the cast to AutonomyLevel safe.


66-79: LGTM on save handling.

The handleSave callback properly:

  • Clears previous errors before attempting save
  • Resets the dirty flag on success to allow future config syncs
  • Catches exceptions with user-friendly error extraction

Comment on lines +26 to +32
vi.mock('@/hooks/useWebSocket', () => ({
useWebSocket: vi.fn().mockReturnValue({
connected: true,
reconnectExhausted: false,
setupError: null,
}),
}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Deduplicate the repeated WebSocket mock payload shape.

The same { connected, reconnectExhausted, setupError } object is repeated in multiple places. Extracting a shared constant reduces drift risk.

♻️ Suggested refactor
+const defaultWebSocketMockState = {
+  connected: true,
+  reconnectExhausted: false,
+  setupError: null as string | null,
+}
+
 vi.mock('@/hooks/useWebSocket', () => ({
-  useWebSocket: vi.fn().mockReturnValue({
-    connected: true,
-    reconnectExhausted: false,
-    setupError: null,
-  }),
+  useWebSocket: vi.fn().mockReturnValue(defaultWebSocketMockState),
 }))
 ...
     afterEach(async () => {
       const { useWebSocket } = await import('@/hooks/useWebSocket')
-      vi.mocked(useWebSocket).mockReturnValue({
-        connected: true,
-        reconnectExhausted: false,
-        setupError: null,
-      })
+      vi.mocked(useWebSocket).mockReturnValue(defaultWebSocketMockState)
     })
 ...
       vi.mocked(useWebSocket).mockReturnValue({
-        connected: false,
-        reconnectExhausted: false,
+        ...defaultWebSocketMockState,
+        connected: false,
         setupError: 'Auth token expired',
       })

Also applies to: 141-147, 152-156

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

In `@web/src/__tests__/hooks/useOrgEditData.test.ts` around lines 26 - 32, Extract
the repeated mock payload for the useWebSocket mock into a shared constant
(e.g., WEB_SOCKET_MOCK or webSocketMockPayload) and replace inline object
literals used in vi.mock(...) and other test mocks in useOrgEditData.test.ts
(locations around the useWebSocket mock and the blocks referenced at lines
~141-147 and ~152-156) with that constant; update any imports or test-scoped
declarations so the constant is visible to all mocks in the file and ensure all
existing mocks (useWebSocket) return that shared constant to avoid duplication
and drift.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 28, 2026 09:04 — 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.

♻️ Duplicate comments (3)
web/src/pages/org-edit/GeneralTab.tsx (3)

26-31: ⚠️ Potential issue | 🟠 Major

Use camelCase for local form state identifiers and map to API shape only at submit.

Local state keys are still snake_case (company_name, autonomy_level, etc.). Keep component-local identifiers camelCase and convert to snake_case only when building UpdateCompanyRequest in handleSave.

♻️ Suggested direction
 interface FormState {
-  company_name: string
-  autonomy_level: AutonomyLevel
-  budget_monthly: number
-  communication_pattern: string
+  companyName: string
+  autonomyLevel: AutonomyLevel
+  budgetMonthly: number
+  communicationPattern: string
 }

 await onUpdate({
-  company_name: form.company_name.trim() || undefined,
-  autonomy_level: form.autonomy_level,
-  budget_monthly: form.budget_monthly,
-  communication_pattern: form.communication_pattern.trim() || undefined,
+  company_name: form.companyName.trim() || undefined,
+  autonomy_level: form.autonomyLevel,
+  budget_monthly: form.budgetMonthly,
+  communication_pattern: form.communicationPattern.trim() || undefined,
 })

As per coding guidelines: "**/*.{ts,tsx,js}: Use camelCase for variable names, function names, and identifiers".

Also applies to: 40-45, 72-75, 92-93, 100-103, 108-109, 118-119

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 26 - 31, FormState and
the component-local state use snake_case identifiers (company_name,
autonomy_level, budget_monthly, communication_pattern); rename these to
camelCase (companyName, autonomyLevel, budgetMonthly, communicationPattern)
throughout the component and update any usages, then in handleSave convert the
camelCase fields into the API shape (build the UpdateCompanyRequest with
snake_case keys) before sending; make sure to update any references in
FormState, the state initializer, input bindings, and the handleSave mapping to
use the new symbols (FormState, companyName, autonomyLevel, budgetMonthly,
communicationPattern, handleSave, UpdateCompanyRequest).

89-90: ⚠️ Potential issue | 🟠 Major

Replace hardcoded spacing/font-size utilities with semantic tokens.

Line 89 (space-y-5), Line 124 (text-xs), and Line 128 (mr-2, size-4) use hardcoded utility sizes. Replace with project semantic spacing/typography/size tokens or CSS variables.

As per coding guidelines: "web/src/**/*.{ts,tsx}: Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))".

Also applies to: 124-124, 128-128

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 89 - 90, In GeneralTab
(the component containing the div with className "space-y-5" and the elements
using "text-xs", "mr-2", and "size-4"), replace the hardcoded Tailwind utilities
with the project's semantic tokens: swap "space-y-5" for the appropriate spacing
token (e.g., gap-section-gap or p-card as used in the project), replace
"text-xs" with the semantic typography token (e.g., text-foreground-sm or the
project's equivalent), and replace "mr-2" / "size-4" with the corresponding
margin/size semantic classes (e.g., gap-*, p-*, or size tokens used across the
codebase); update the JSX in GeneralTab and any child components (e.g., the
InputField wrapper) to use those semantic class names or CSS variables instead
of numeric utilities.

39-134: 🛠️ Refactor suggestion | 🟠 Major

Split GeneralTab to comply with the 50-line function limit.

GeneralTab currently exceeds the function length cap. Extract form rendering and/or form state logic into a dedicated component/hook.

As per coding guidelines: "**/*.{py,ts,tsx,go}: Functions should be under 50 lines; files should be under 800 lines".

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

In `@web/src/pages/org-edit/GeneralTab.tsx` around lines 39 - 134, GeneralTab is
over the 50-line limit; extract the form state and handlers into a new hook
(e.g., useGeneralForm) or split the render into a child component (e.g.,
GeneralForm) to shorten GeneralTab. Move useState usages (form, dirty,
submitError), useEffect sync logic, updateForm, and handleSave into
useGeneralForm (exporting form, dirty, submitError, updateForm, handleSave) or
into a child component that receives config, onUpdate, and saving as props; then
have GeneralTab simply return the EmptyState check and render SectionCard with
the new GeneralForm or call the hook and pass returned values to a small
presentational component, ensuring you reference the existing symbols
(GeneralTab, useEffect, updateForm, handleSave) so behavior is unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web/src/pages/org-edit/GeneralTab.tsx`:
- Around line 26-31: FormState and the component-local state use snake_case
identifiers (company_name, autonomy_level, budget_monthly,
communication_pattern); rename these to camelCase (companyName, autonomyLevel,
budgetMonthly, communicationPattern) throughout the component and update any
usages, then in handleSave convert the camelCase fields into the API shape
(build the UpdateCompanyRequest with snake_case keys) before sending; make sure
to update any references in FormState, the state initializer, input bindings,
and the handleSave mapping to use the new symbols (FormState, companyName,
autonomyLevel, budgetMonthly, communicationPattern, handleSave,
UpdateCompanyRequest).
- Around line 89-90: In GeneralTab (the component containing the div with
className "space-y-5" and the elements using "text-xs", "mr-2", and "size-4"),
replace the hardcoded Tailwind utilities with the project's semantic tokens:
swap "space-y-5" for the appropriate spacing token (e.g., gap-section-gap or
p-card as used in the project), replace "text-xs" with the semantic typography
token (e.g., text-foreground-sm or the project's equivalent), and replace "mr-2"
/ "size-4" with the corresponding margin/size semantic classes (e.g., gap-*,
p-*, or size tokens used across the codebase); update the JSX in GeneralTab and
any child components (e.g., the InputField wrapper) to use those semantic class
names or CSS variables instead of numeric utilities.
- Around line 39-134: GeneralTab is over the 50-line limit; extract the form
state and handlers into a new hook (e.g., useGeneralForm) or split the render
into a child component (e.g., GeneralForm) to shorten GeneralTab. Move useState
usages (form, dirty, submitError), useEffect sync logic, updateForm, and
handleSave into useGeneralForm (exporting form, dirty, submitError, updateForm,
handleSave) or into a child component that receives config, onUpdate, and saving
as props; then have GeneralTab simply return the EmptyState check and render
SectionCard with the new GeneralForm or call the hook and pass returned values
to a small presentational component, ensuring you reference the existing symbols
(GeneralTab, useEffect, updateForm, handleSave) so behavior is unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e48d8626-5fdd-4c34-9259-392053df4f0e

📥 Commits

Reviewing files that changed from the base of the PR and between 8057b7d and db59db4.

📒 Files selected for processing (1)
  • web/src/pages/org-edit/GeneralTab.tsx
📜 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: Dashboard Test
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript, use Pydantic v2 models for Python
Always use async/await for promises; prefer asyncio.TaskGroup for parallel operations in Python
Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in .tsx/.ts files

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable names, function names, and identifiers

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Functions should be under 50 lines; files should be under 800 lines

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Files:

  • web/src/pages/org-edit/GeneralTab.tsx
🧠 Learnings (6)
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx,js} : Use camelCase for variable names, function names, and identifiers

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/**/*.{ts,tsx} : Never hardcode colors, fonts, or spacing - always use Tailwind semantic classes (text-foreground, bg-card, p-card, gap-section-gap) or CSS variables (var(--so-accent))

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to **/*.{ts,tsx} : Use design tokens exclusively - no hardcoded hex colors, rgba values, font-family declarations, or pixel spacing values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 Learning: 2026-03-27T22:44:47.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T22:44:47.320Z
Learning: Applies to web/src/components/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` (StatusBadge, MetricCard, SectionCard, AgentCard, Avatar, Button, Toast, etc.) before creating new ones

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to web/**/*.vue : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in dashboard code — use generic names: `example-provider`, `example-large-001`, etc.

Applied to files:

  • web/src/pages/org-edit/GeneralTab.tsx
🔇 Additional comments (1)
web/src/pages/org-edit/GeneralTab.tsx (1)

49-58: Dirty-guard hydration is implemented correctly.

Line 50’s config && !dirty gate prevents polling/WebSocket refreshes from overwriting in-progress edits while still allowing initial hydration.

@Aureliolo Aureliolo merged commit cfb88b0 into main Mar 28, 2026
32 checks passed
@Aureliolo Aureliolo deleted the feat/company-page branch March 28, 2026 09:11
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 28, 2026 09:11 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 28, 2026
)

## Summary

- **Login page** (#789): Branded login form with first-run admin
creation flow, setup status detection, lockout protection, and redirect
logic
- **Agent bootstrap** (#789): New `bootstrap.py` module that loads
persisted `AgentConfig` from `ConfigResolver` and registers
`AgentIdentity` instances in the runtime registry -- runs at app startup
and after setup completion
- **Engine re-init** (#789): `_post_setup_reinit` reloads provider
registry and bootstraps agents after `POST /setup/complete`
- **Company page** (#888): Full org editor at `/org/edit` with three-tab
interface (General, Agents, Departments), CRUD via Drawer/Dialog,
drag-drop reordering via @dnd-kit, GUI/YAML editor toggle, Zustand store
with 9 mutation actions + 2 optimistic helpers
- **API contract fix**: Added `TemplateVariableResponse` to backend
`TemplateInfoResponse`, closing frontend/backend drift where frontend
expected `variables` field the backend didn't serve

## Test plan

- [x] 10933 Python tests pass (8 new: bootstrap unit tests,
`_maybe_bootstrap_agents` tests, provider reload test, integration test)
- [x] 1467 Vitest tests pass (1 new pristine-save test, 1 App.test fix)
- [x] mypy strict -- no issues in 1230 files
- [x] TypeScript type-check clean
- [x] ESLint clean (0 errors)
- [x] ruff lint + format clean
- [x] Pre-commit + pre-push hooks pass
- [x] Integration test: full first-run flow (admin create -> login ->
company -> agent -> complete -> agents registered)
- [ ] Visual verification at `http://localhost:5173/login` and
`/org/edit`

## Review coverage

Pre-reviewed by 16 agents (code-reviewer, python-reviewer,
logging-audit, resilience-audit, conventions-enforcer,
silent-failure-hunter, async-concurrency-reviewer, comment-analyzer,
type-design-analyzer, security-reviewer, frontend-reviewer,
api-contract-drift, docs-consistency, pr-test-analyzer,
test-quality-reviewer, issue-resolution-verifier). 30 findings
identified and addressed in review fixes commit.

Closes #789
Closes #888

Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

feat: Company page (department/agent management, consistent cards)

1 participant