feat: Introduce Kaizen Web UI and Management Dashboard#77
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Vite/React frontend (sources, components, tests, build/config), a FastAPI UI/API router, serves the built UI under /ui/ from the MCP FastAPI app, starts that app in a background thread alongside the MCP server, and updates CI, packaging, and release steps to build/test/include the frontend dist. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser (React UI)
participant FastAPI as FastAPI App (/api)
participant MCP as MCP Server (main thread)
participant Milvus as Milvus / Store
Browser->>FastAPI: GET /api/dashboard
FastAPI->>Milvus: query namespaces, stats, recent entities
Milvus-->>FastAPI: return data or error
FastAPI-->>Browser: JSON dashboard payload
Browser->>FastAPI: POST /api/namespaces
FastAPI->>MCP: use MCP client to insert/modify entities/namespaces
MCP-->>FastAPI: ack/response
FastAPI-->>Browser: success/failure
Note over MCP, FastAPI: FastAPI app is started in a background daemon thread while MCP runs on main thread
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (15)
.github/workflows/check-code.yaml (1)
96-98: Usenpm ciinstead ofnpm installin CI.
npm installcan silently updatepackage-lock.jsonand install out-of-range versions, making the CI environment non-reproducible.npm ciinstalls strictly from the lockfile, is faster, and fails loudly if the lockfile is stale — all properties desirable in CI. This also aligns with the cache key already being based onpackage-lock.json.♻️ Proposed fix
- name: Install dependencies working-directory: kaizen/frontend/ui - run: npm install + run: npm ci🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/check-code.yaml around lines 96 - 98, Replace the CI step that currently runs "npm install" in the "Install dependencies" step for the kaizen/frontend/ui working-directory with "npm ci" so the job installs strictly from package-lock.json (faster, reproducible, and fails if the lockfile is stale); update the run command in that step accordingly and keep the step name and working-directory unchanged.kaizen/frontend/ui/tsconfig.app.json (1)
12-16: Test-only types pollute the production source type scope.
"vitest/globals"and"@testing-library/jest-dom"in the app tsconfig make test globals (describe,it,expect,toBeInTheDocument, etc.) available in every production source file. A dedicatedtsconfig.vitest.json(or adding these types only invite.config.ts'stest.globalssetting) keeps test types scoped to test files.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/tsconfig.app.json` around lines 12 - 16, Remove test-only type entries from the app TypeScript config: delete "vitest/globals" and "@testing-library/jest-dom" from the "types" array in tsconfig.app.json, and instead create a dedicated tsconfig.vitest.json that extends the app config and adds those two entries to its "types" array (or configure them via vite.config.ts test.globals). Update your test runner to use tsconfig.vitest.json so test globals like describe/it/expect and jest-dom matchers are only visible in test files.kaizen/frontend/ui/src/App.tsx (1)
37-41: No catch-all route — unknown paths render a blank page.Any URL that doesn't match
/,/namespaces, or/namespaces/:id/entities(e.g., a mistyped path or a stale bookmark) silently renders nothing.✏️ Suggested fix
<Route path="/namespaces/:id/entities" element={<EntityExplorer />} /> + <Route path="*" element={<p style={{ padding: '2rem' }}>Page not found.</p>} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/App.tsx` around lines 37 - 41, The router currently only defines Routes for "/", "/namespaces", and "/namespaces/:id/entities" so unknown paths render a blank page; add a catch‑all Route (path="*") to the <Routes> list (after the existing <Route ... /> entries) that either redirects to a safe route (e.g., "/" or "/namespaces") or renders a simple NotFound component; if NotFound doesn't exist, create a minimal NotFound component and import it into App.tsx and use it as the element for the catch‑all Route so mistyped/stale URLs show a helpful page instead of blank output (references: Routes, Route, Dashboard, Namespaces, EntityExplorer).kaizen/frontend/ui/src/main.tsx (1)
6-9: Consider guarding against a missing#rootelement.The
!non-null assertion ongetElementById('root')silently passes TypeScript but throws an unhandledTypeErrorat runtime if the element is ever absent (e.g., the HTML is served incorrectly or SSR-injected). A one-line guard prevents a cryptic blank-screen failure.🛡️ Suggested fix
-createRoot(document.getElementById('root')!).render( +const rootEl = document.getElementById('root'); +if (!rootEl) throw new Error('Root element `#root` not found'); +createRoot(rootEl).render( <StrictMode> <App /> </StrictMode>, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/main.tsx` around lines 6 - 9, The current call uses a non-null assertion on document.getElementById('root') which will throw at runtime if the element is missing; update the bootstrap to first query const rootEl = document.getElementById('root') and guard it before calling createRoot(rootEl). If rootEl is null, handle gracefully (e.g., log an error via console.error or a logger and return/abort) instead of passing the non-null assertion to createRoot(render <App />), keeping the rest (StrictMode, App) unchanged.kaizen/frontend/ui/src/components/EntityExplorer.test.tsx (1)
28-72: Missing test coverage for error state and delete flow.The suite covers loader, happy-path render, and modal open, but the following paths in
EntityExplorer.tsxhave no coverage:
fetchreturning a non-okresponse → error banner rendered- Delete confirmation and subsequent
fetchcallAs per learnings, all new features need tests (unit + e2e where applicable).
Do you want me to draft the missing test cases?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/EntityExplorer.test.tsx` around lines 28 - 72, Add two tests to EntityExplorer.test.tsx: one that mocks globalThis.fetch to return a non-ok response for the initial fetch and asserts the error banner from EntityExplorer is rendered (use renderWithRouter and waitFor to check the error banner text); and one that covers the delete flow by mocking the initial fetch to return entities, rendering EntityExplorer, using userEvent to click the entity's delete button which opens the confirmation dialog, then mocking the delete request (fetch) to return ok:true and the subsequent refetch to return the updated list (or empty), and asserting the deleted entity is no longer in the document and any success UI is shown. Reference the existing helpers and symbols: EntityExplorer component, renderWithRouter, globalThis.fetch mocking, userEvent.setup(), and any confirmation modal text used by the component.kaizen/frontend/ui/src/components/Namespaces.test.tsx (1)
9-70: Missing test coverage for error state, create modal, and delete flow.The suite covers loader, successful list render, and empty state but omits:
fetchreturning a non-okresponse → error banner + retry- "Create Namespace" modal open, input validation, and successful submission
- Delete confirmation dialog and
DELETE /api/namespaces/{id}callAs per learnings, all new features need tests (unit + e2e where applicable).
Do you want me to draft the missing test cases?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/Namespaces.test.tsx` around lines 9 - 70, Add unit tests for the Namespaces component to cover the missing error, create, and delete flows: mock globalThis.fetch to return a non-ok response and assert the error banner and retry behavior for the initial GET; simulate clicking the "Create Namespace" button to open the create modal, assert input validation behavior (e.g., required name), then mock a successful POST to the create endpoint and assert the new row appears and modal closes; simulate clicking the namespace "Delete" control to open the confirmation dialog, mock DELETE /api/namespaces/{id} and assert the request is made and the row is removed. Reference the Namespaces component, the existing loader test id ('loader'), the "Create Namespace" button text, and the DELETE endpoint to locate where to add these tests.kaizen/frontend/ui/src/components/EntityExplorer.tsx (2)
158-170: Dead try/catch — object literal construction cannot throw.The
try/catchwrapping a plain object literal assignment is unreachable. This block just buildsparsedMetadatafrom local variables — no parsing, no I/O, no potential exception.♻️ Proposed simplification
} else if (activeType === "policy") { if (!policyName.trim() || !policyDesc.trim() || policyTriggers.length === 0) { setCreateError("Policies require a name, description, and at least one trigger."); return; } - try { - parsedMetadata = { - name: policyName, - description: policyDesc, - policy_type: policyTypeEnum, - priority: policyPriority, - enabled: policyEnabled, - triggers: policyTriggers - }; - } catch (err) { - setCreateError("Failed to build policy metadata payload."); - return; - } + parsedMetadata = { + name: policyName, + description: policyDesc, + policy_type: policyTypeEnum, + priority: policyPriority, + enabled: policyEnabled, + triggers: policyTriggers + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx` around lines 158 - 170, Remove the dead try/catch around constructing parsedMetadata — the object literal using policyName, policyDesc, policyTypeEnum, policyPriority, policyEnabled, and policyTriggers cannot throw; replace the try/catch by a direct assignment to parsedMetadata, and if you need runtime validation instead, add explicit checks (e.g., validate policyName, policyTypeEnum) and call setCreateError on validation failures before assigning parsedMetadata.
98-102:fetchEntitiesis missing from useEffect dependency array.
fetchEntitiesis called inside thisuseEffectbut is not listed as a dependency. While it works because the deps[id, filterType]trigger re-renders that recreate the closure, linting rules (react-hooks/exhaustive-deps) will flag this. Consider inlining the fetch logic or wrappingfetchEntitiesinuseCallback.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx` around lines 98 - 102, The useEffect calls fetchEntities but doesn't include it in the dependency array, which triggers react-hooks/exhaustive-deps lint errors; fix by either inlining the fetch logic inside the useEffect or memoizing fetchEntities with useCallback and then adding fetchEntities to the dependency array so the hook depends on the stable callback (update the fetchEntities declaration to useCallback or move its body into the useEffect that currently depends on id and filterType).kaizen/frontend/mcp/mcp_server.py (1)
55-63: Generic/assetsmount path may conflict with non-UI routes.Mounting at
/assetsis a very broad path. If any future route or static mount uses/assets, this will shadow it. Consider mounting under/ui/assetsto namespace it properly, and adjusting the Vitebaseconfig accordingly.♻️ Proposed change
assets_dir = os.path.join(ui_dist_dir, "assets") if os.path.exists(assets_dir): - app.mount("/assets", StaticFiles(directory=assets_dir), name="ui_assets") + app.mount("/ui/assets", StaticFiles(directory=assets_dir), name="ui_assets")This requires setting
base: '/ui/'in the Vite config so the built HTML references/ui/assets/...instead of/assets/....🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/mcp/mcp_server.py` around lines 55 - 63, Change the broad /assets mount to a namespaced path to avoid shadowing other routes: update the app.mount call that uses assets_dir and StaticFiles to mount at "/ui/assets" (and likewise change the secondary mount to "/ui/ui_static" or keep "/ui_static" if desired) and update Vite's base config to "base: '/ui/'" so index.html references /ui/assets/...; specifically modify the code around assets_dir, app.mount("/assets", StaticFiles(...), name="ui_assets") and any place that assumes /assets to use the new /ui/assets namespace.kaizen/frontend/ui/src/index.css (1)
22-27: Stylelint: remove quotes aroundInterin font-family declarations.Stylelint reports
font-family-name-quoteserrors at lines 23 and 131. Per the rule, known font family names should not be quoted.♻️ Proposed fix (line 23)
- font-family: 'Inter', sans-serif; + font-family: Inter, sans-serif;Apply the same change at line 131.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/index.css` around lines 22 - 27, Remove the unnecessary quotes around the font family name in the font-family declarations to satisfy Stylelint's font-family-name-quotes rule: update the body selector's font-family and the other rule at line 131 to use Inter without quotes (i.e., font-family: Inter, sans-serif;), ensuring both occurrences (the body selector and the other CSS rule that currently uses 'Inter') are changed to the unquoted form.kaizen/frontend/mcp/__main__.py (1)
6-12: Hardcoded port with no configuration or user feedback on failure.Port
8000is hardcoded and if it's already in use, the server silently fails with only alogging.errorthat may be missed sincelog_level="warning"suppresses uvicorn's own output. Consider making the port configurable (e.g., via an environment variable) and logging the listening URL at a level that's visible to the user.♻️ Proposed improvement
+import os + def run_api_server(): """Run the FastAPI server for UI and API in a background thread.""" + port = int(os.environ.get("KAIZEN_UI_PORT", "8000")) try: - # We run with log_level="warning" to avoid cluttering stdio for MCP - uvicorn.run(app, host="127.0.0.1", port=8000, log_level="warning") + logging.info(f"Starting Kaizen UI server on http://127.0.0.1:{port}") + uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning") except Exception as e: logging.error(f"Failed to start UI server: {e}")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/mcp/__main__.py` around lines 6 - 12, run_api_server currently hardcodes port 8000 and suppresses useful startup output; make the port configurable (read from an environment variable like MCP_UI_PORT or a passed-in parameter) and use that value in uvicorn.run(app, host="127.0.0.1", port=..., log_level=...). After starting (or in the except block), log the listening URL (e.g., "UI server listening at http://127.0.0.1:PORT") at an INFO level so users see it, and ensure the except block logs the full exception (use logging.exception or include traceback) so failures like "port in use" are visible even when uvicorn log_level is "warning".kaizen/frontend/api/routes.py (3)
79-99: Inconsistent namespace listing: Milvus path bypasses the client abstraction.The Milvus branch (line 85-92) directly accesses
client.backend.milvusto list collections and get stats, bypassing theKaizenClientAPI. This couples the route to the Milvus backend internals. If the Milvus backend changes its attribute name or collection semantics, this route breaks silently.The non-Milvus path correctly uses
client.all_namespaces(). Consider using the client abstraction for both paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 79 - 99, The route handler list_namespaces is bypassing the KaizenClient abstraction by directly calling client.backend.milvus (client.backend.milvus.list_collections and get_collection_stats); change the Milvus-specific branch to use the client API (e.g., client.all_namespaces or a new client.list_namespaces/list_namespace_stats method) so both paths go through the KaizenClient abstraction (adjust or add a method on KaizenClient if needed), map its results to the same {"id": ..., "amount_of_entities": ...} shape, and remove direct references to client.backend.milvus in list_namespaces while preserving the same error handling and return behavior.
109-111:HTTPExceptionimported locally in multiple places — consolidate at module level.
HTTPExceptionis imported inside four separateexcept/tryblocks. Move it to the top-level import alongsideAPIRouterandQueryto reduce noise and avoid repeated import overhead on error paths.♻️ Proposed fix
-from fastapi import APIRouter, Query +from fastapi import APIRouter, HTTPException, QueryThen remove all
from fastapi import HTTPExceptionlines inside the functions.As per coding guidelines,
**/*.py: Use Ruff for linting and formatting (configured in pyproject.toml).Also applies to: 121-123, 165-167, 211-213
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 109 - 111, Add HTTPException to the module-level imports next to APIRouter and Query (e.g., "from fastapi import APIRouter, Query, HTTPException") and remove the local "from fastapi import HTTPException" statements currently inside handlers (the ones that log "Error creating namespace: {e}" and similar except blocks). Update references in functions using HTTPException to rely on the top-level import so there are no repeated local imports.
17-17: Mutable default argument in Pydantic model.
metadata: dict = {}uses a mutable default. While Pydantic v2 handles this safely internally, it's still flagged by linters (including Ruff). UseField(default_factory=dict)for clarity and consistency with theEntityschema inkaizen/schema/core.py(line 23).♻️ Proposed fix
+from pydantic import BaseModel, Field + class EntityCreateRequest(BaseModel): type: str content: str - metadata: dict = {} + metadata: dict = Field(default_factory=dict)As per coding guidelines,
**/*.py: Use Ruff for linting and formatting (configured in pyproject.toml).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` at line 17, Replace the mutable default metadata: dict = {} with a Pydantic default factory: use Field(default_factory=dict) and add the necessary import (Field) from pydantic so the model uses an immutable factory-backed default; mirror the pattern used in the Entity schema (kaizen/schema/core.py) to satisfy linters like Ruff and avoid mutable default arguments.kaizen/frontend/ui/src/components/Dashboard.tsx (1)
33-47: Missing fetch cleanup — potential state update after unmount.The
useEffectfetch doesn't use anAbortController, so if the component unmounts mid-request,setData/setLoading/setErrorwill be called on an unmounted component. While React 18+ removed the console warning, it's still a best practice to abort in-flight requests on cleanup.♻️ Proposed improvement
useEffect(() => { + const controller = new AbortController(); - fetch('/api/dashboard') + fetch('/api/dashboard', { signal: controller.signal }) .then((res) => { if (!res.ok) throw new Error('Failed to fetch dashboard data'); return res.json(); }) .then((d) => { setData(d); setLoading(false); }) .catch((err) => { + if (err.name === 'AbortError') return; setError(err.message || 'Error occurred'); setLoading(false); }); + return () => controller.abort(); }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/Dashboard.tsx` around lines 33 - 47, The useEffect fetch call should be cancelled on unmount to avoid state updates after unmount: create an AbortController, pass controller.signal to fetch('/api/dashboard'), and in the effect cleanup call controller.abort(); in the fetch promise handlers ignore/handle the abort (e.g., if error.name === 'AbortError' skip calling setData/setLoading/setError) so references to setData, setLoading, and setError inside the effect (the fetch .then/.catch blocks) only run for non-aborted responses.
🤖 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/integrations/roo-code/Kaizen-export.yaml`:
- Around line 62-63: The docs are inconsistent about save_trajectory's
signature; standardize all mentions to indicate
save_trajectory(trajectory_data[, task_id]) with task_id explicitly marked
optional—update the text near the current save_trajectory() call and the
occurrences referenced by save_trajectory(…, task_id) so they all state the same
optional-parameter form, and ensure any examples show both the minimal call
save_trajectory(trajectory_data) and the variant with
save_trajectory(trajectory_data, task_id); adjust any wording that implies
task_id is required to clarify it is optional.
In `@kaizen/backend/milvus.py`:
- Around line 80-83: The try/except that sets namespace.num_entities = 0
swallows errors—change both try blocks that call
self.milvus.get_collection_stats(namespace_id) to catch Exception as e and call
logger.warning (or the module logger) including the namespace_id and exception
(e) or use logger.exception to preserve stacktrace, then keep setting
namespace.num_entities = 0; ensure you update both occurrences so Milvus
connection/auth/collection errors are logged for observability.
In `@kaizen/frontend/api/routes.py`:
- Around line 46-64: The dashboard currently iterates over namespaces and calls
client.get_all_entities(ns.id, limit=1000), accumulating total_entities,
type_breakdown and recent_entities which can OOM/timeout for large data sets;
replace this full-scan by using a backend-side aggregation or caching layer
(e.g., a new client method like client.get_namespace_entity_counts or
client.get_entities_summary) to fetch counts and a small sample per-namespace
instead of all entities, and update the route to use that aggregated summary for
total_entities and type_breakdown rather than looping over every entity; also
make the content-preview safe by checking the type of entity.content before
slicing in the recent_entities construction (use the string-only slice for str,
otherwise serialize/truncate a safe representation) and keep the existing
try/except logging around the namespace fetches.
- Around line 185-196: The Policy constructor call can raise TypeError if
req.metadata contains a "type" key (duplicate keyword), so before calling
Policy(content=req.content, type=req.type, **req.metadata) make a shallow copy
of req.metadata, pop/remove the "type" key from that copy, and pass the
sanitized dict to Policy; apply the same defensive pattern for the Tip
validation (remove "content" from a copy of req.metadata before calling Tip(...,
content=req.content, **sanitized_metadata)) to avoid duplicate-argument errors
while not mutating the original req.metadata.
In `@kaizen/frontend/mcp/mcp_server.py`:
- Around line 70-82: The serve_spa catch-all currently joins catchall to
ui_dist_dir (potential_file) without sanitizing, enabling path traversal; fix by
resolving both ui_dist_dir and the requested path (use
pathlib.Path(...).resolve() or os.path.realpath) and verify the resolved
requested path is a descendant of the resolved ui_dist_dir (e.g., compare with
os.path.commonpath or Path.is_relative_to), only then call
is_file()/os.path.isfile and return FileResponse; if the check fails or the file
is absent, fall back to serving the resolved index_file (also resolved) or raise
HTTPException(404) to block traversal attempts.
In `@kaizen/frontend/ui/index.html`:
- Around line 5-7: Update the placeholder HTML title and favicon: change the
<title> element currently set to "ui" to the proper Kaizen product name/brand,
and replace the <link rel="icon" ... href="/vite.svg"> reference with the Kaizen
favicon path (e.g., /assets/kaizen-favicon.ico or the SVG for Kaizen) so the
page shows the correct brand and icon.
In `@kaizen/frontend/ui/src/App.tsx`:
- Around line 22-23: The Namespaces nav Link in App.tsx only checks exact
equality (location.pathname === '/namespaces') so it won't be active on
sub-routes; change the active check to include subpaths (e.g., use
location.pathname.startsWith('/namespaces') or use react-router's
useMatch/useResolvedPath to match '/namespaces/*') so the Link component (Link
to="/namespaces") renders with the 'active' class when on any /namespaces or
/namespaces/:id/... route.
In `@kaizen/frontend/ui/src/components/EntityExplorer.test.tsx`:
- Around line 8-16: The test currently sets globalThis.confirm once at module
scope but calls vi.resetAllMocks() in beforeEach which clears mock
implementations; move the globalThis.confirm = vi.fn(() => true) assignment into
the beforeEach (after vi.resetAllMocks()) so the confirm stub is re-applied for
every test, ensuring handleDelete sees a truthy confirmation in each run; leave
the global fetch mock as-is or also re-establish it in beforeEach if you want
its implementation reset per-test.
In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx`:
- Around line 335-342: Guard against null/undefined metadata before calling
Object.keys and JSON.stringify: replace the direct use of
Object.keys(selectedEntity.metadata) and JSON.stringify(selectedEntity.metadata,
...) with null-safe checks (e.g., use selectedEntity.metadata ?? {} for
Object.keys and for rendering) or use optional chaining like
selectedEntity.metadata && Object.keys(selectedEntity.metadata).length > 0;
ensure the rendered JSON also receives a non-null value (e.g.,
selectedEntity.metadata ?? {}) so JSON.stringify won't throw. Target the block
referencing selectedEntity.metadata and JSON.stringify in EntityExplorer.tsx to
implement the nullish coalescing/optional chaining fix.
- Around line 550-556: The onChange handler currently sets updateTrigger(idx,
'value', val.includes(',') ? arr : val), causing t.value to be sometimes string
and sometimes string[]; always normalize to an array: when handling the input in
the onChange for the EntityExplorer component (the input using t.value and
calling updateTrigger), parse the input value into an array (split on commas,
trim, and for single token still wrap as [token], and use an empty array for
empty input) and pass that array to updateTrigger(idx, 'value', parsedArray) so
t.value is consistently a string[] across updateTrigger and downstream payloads.
In `@kaizen/frontend/ui/src/components/Namespaces.test.tsx`:
- Around line 49-53: The test is asserting a bare '0' with
screen.getByText('0'), which is fragile; instead scope the lookup to the
specific namespace row or cell so it only matches the intended table cell.
Locate the row containing 'test-namespace-2' (or the table element rendered in
Namespaces.test.tsx), then use a scoped query (e.g., within(row).getByText or a
getByRole on the specific cell) to assert the '0' value for that namespace so
the assertion targets the correct element rather than any DOM node containing
"0".
In `@kaizen/frontend/ui/src/components/Namespaces.tsx`:
- Around line 162-186: The modal keeps stale state because newName and
createError aren't cleared when toggling isCreateOpen; update the cancel handler
and modal-open logic to reset both values: call setNewName('') and
setCreateError('') when closing via the Cancel button (the onClick that calls
setIsCreateOpen(false)) and also clear them when opening the modal (where you
set setIsCreateOpen(true)) or add a useEffect watching isCreateOpen in the
Namespaces component to run setNewName('') and setCreateError('') whenever
isCreateOpen becomes false/true as appropriate; reference newName, setNewName,
createError, setCreateError, isCreateOpen, setIsCreateOpen and handleCreate to
place the resets.
In `@kaizen/frontend/ui/src/index.css`:
- Line 1: Fix the broken Google Fonts import in index.css: change the domain
from fonts.googleapis.css2 to fonts.googleapis.com/css2 and replace the url(...)
notation with a quoted string per stylelint import-notation (i.e., use "@import
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap';").
Update the existing `@import` line accordingly so the Inter font will load and
satisfy Stylelint.
In `@MANIFEST.in`:
- Line 1: The package manifest references the git-ignored frontend assets via
"recursive-include kaizen/frontend/ui/dist *" but those files are not built by
default; add an automated pre-packaging step to run the frontend build (e.g.,
run npm ci && npm run build in kaizen/frontend/ui) before creating an sdist —
implement this as a hatch build hook (pre_build) in pyproject.toml or as a
Makefile target invoked by your CI/release scripts so that the dist directory is
created consistently, and optionally add a sanity check in your packaging script
to fail if kaizen/frontend/ui/dist is missing to prevent empty sdists.
---
Nitpick comments:
In @.github/workflows/check-code.yaml:
- Around line 96-98: Replace the CI step that currently runs "npm install" in
the "Install dependencies" step for the kaizen/frontend/ui working-directory
with "npm ci" so the job installs strictly from package-lock.json (faster,
reproducible, and fails if the lockfile is stale); update the run command in
that step accordingly and keep the step name and working-directory unchanged.
In `@kaizen/frontend/api/routes.py`:
- Around line 79-99: The route handler list_namespaces is bypassing the
KaizenClient abstraction by directly calling client.backend.milvus
(client.backend.milvus.list_collections and get_collection_stats); change the
Milvus-specific branch to use the client API (e.g., client.all_namespaces or a
new client.list_namespaces/list_namespace_stats method) so both paths go through
the KaizenClient abstraction (adjust or add a method on KaizenClient if needed),
map its results to the same {"id": ..., "amount_of_entities": ...} shape, and
remove direct references to client.backend.milvus in list_namespaces while
preserving the same error handling and return behavior.
- Around line 109-111: Add HTTPException to the module-level imports next to
APIRouter and Query (e.g., "from fastapi import APIRouter, Query,
HTTPException") and remove the local "from fastapi import HTTPException"
statements currently inside handlers (the ones that log "Error creating
namespace: {e}" and similar except blocks). Update references in functions using
HTTPException to rely on the top-level import so there are no repeated local
imports.
- Line 17: Replace the mutable default metadata: dict = {} with a Pydantic
default factory: use Field(default_factory=dict) and add the necessary import
(Field) from pydantic so the model uses an immutable factory-backed default;
mirror the pattern used in the Entity schema (kaizen/schema/core.py) to satisfy
linters like Ruff and avoid mutable default arguments.
In `@kaizen/frontend/mcp/__main__.py`:
- Around line 6-12: run_api_server currently hardcodes port 8000 and suppresses
useful startup output; make the port configurable (read from an environment
variable like MCP_UI_PORT or a passed-in parameter) and use that value in
uvicorn.run(app, host="127.0.0.1", port=..., log_level=...). After starting (or
in the except block), log the listening URL (e.g., "UI server listening at
http://127.0.0.1:PORT") at an INFO level so users see it, and ensure the except
block logs the full exception (use logging.exception or include traceback) so
failures like "port in use" are visible even when uvicorn log_level is
"warning".
In `@kaizen/frontend/mcp/mcp_server.py`:
- Around line 55-63: Change the broad /assets mount to a namespaced path to
avoid shadowing other routes: update the app.mount call that uses assets_dir and
StaticFiles to mount at "/ui/assets" (and likewise change the secondary mount to
"/ui/ui_static" or keep "/ui_static" if desired) and update Vite's base config
to "base: '/ui/'" so index.html references /ui/assets/...; specifically modify
the code around assets_dir, app.mount("/assets", StaticFiles(...),
name="ui_assets") and any place that assumes /assets to use the new /ui/assets
namespace.
In `@kaizen/frontend/ui/src/App.tsx`:
- Around line 37-41: The router currently only defines Routes for "/",
"/namespaces", and "/namespaces/:id/entities" so unknown paths render a blank
page; add a catch‑all Route (path="*") to the <Routes> list (after the existing
<Route ... /> entries) that either redirects to a safe route (e.g., "/" or
"/namespaces") or renders a simple NotFound component; if NotFound doesn't
exist, create a minimal NotFound component and import it into App.tsx and use it
as the element for the catch‑all Route so mistyped/stale URLs show a helpful
page instead of blank output (references: Routes, Route, Dashboard, Namespaces,
EntityExplorer).
In `@kaizen/frontend/ui/src/components/Dashboard.tsx`:
- Around line 33-47: The useEffect fetch call should be cancelled on unmount to
avoid state updates after unmount: create an AbortController, pass
controller.signal to fetch('/api/dashboard'), and in the effect cleanup call
controller.abort(); in the fetch promise handlers ignore/handle the abort (e.g.,
if error.name === 'AbortError' skip calling setData/setLoading/setError) so
references to setData, setLoading, and setError inside the effect (the fetch
.then/.catch blocks) only run for non-aborted responses.
In `@kaizen/frontend/ui/src/components/EntityExplorer.test.tsx`:
- Around line 28-72: Add two tests to EntityExplorer.test.tsx: one that mocks
globalThis.fetch to return a non-ok response for the initial fetch and asserts
the error banner from EntityExplorer is rendered (use renderWithRouter and
waitFor to check the error banner text); and one that covers the delete flow by
mocking the initial fetch to return entities, rendering EntityExplorer, using
userEvent to click the entity's delete button which opens the confirmation
dialog, then mocking the delete request (fetch) to return ok:true and the
subsequent refetch to return the updated list (or empty), and asserting the
deleted entity is no longer in the document and any success UI is shown.
Reference the existing helpers and symbols: EntityExplorer component,
renderWithRouter, globalThis.fetch mocking, userEvent.setup(), and any
confirmation modal text used by the component.
In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx`:
- Around line 158-170: Remove the dead try/catch around constructing
parsedMetadata — the object literal using policyName, policyDesc,
policyTypeEnum, policyPriority, policyEnabled, and policyTriggers cannot throw;
replace the try/catch by a direct assignment to parsedMetadata, and if you need
runtime validation instead, add explicit checks (e.g., validate policyName,
policyTypeEnum) and call setCreateError on validation failures before assigning
parsedMetadata.
- Around line 98-102: The useEffect calls fetchEntities but doesn't include it
in the dependency array, which triggers react-hooks/exhaustive-deps lint errors;
fix by either inlining the fetch logic inside the useEffect or memoizing
fetchEntities with useCallback and then adding fetchEntities to the dependency
array so the hook depends on the stable callback (update the fetchEntities
declaration to useCallback or move its body into the useEffect that currently
depends on id and filterType).
In `@kaizen/frontend/ui/src/components/Namespaces.test.tsx`:
- Around line 9-70: Add unit tests for the Namespaces component to cover the
missing error, create, and delete flows: mock globalThis.fetch to return a
non-ok response and assert the error banner and retry behavior for the initial
GET; simulate clicking the "Create Namespace" button to open the create modal,
assert input validation behavior (e.g., required name), then mock a successful
POST to the create endpoint and assert the new row appears and modal closes;
simulate clicking the namespace "Delete" control to open the confirmation
dialog, mock DELETE /api/namespaces/{id} and assert the request is made and the
row is removed. Reference the Namespaces component, the existing loader test id
('loader'), the "Create Namespace" button text, and the DELETE endpoint to
locate where to add these tests.
In `@kaizen/frontend/ui/src/index.css`:
- Around line 22-27: Remove the unnecessary quotes around the font family name
in the font-family declarations to satisfy Stylelint's font-family-name-quotes
rule: update the body selector's font-family and the other rule at line 131 to
use Inter without quotes (i.e., font-family: Inter, sans-serif;), ensuring both
occurrences (the body selector and the other CSS rule that currently uses
'Inter') are changed to the unquoted form.
In `@kaizen/frontend/ui/src/main.tsx`:
- Around line 6-9: The current call uses a non-null assertion on
document.getElementById('root') which will throw at runtime if the element is
missing; update the bootstrap to first query const rootEl =
document.getElementById('root') and guard it before calling createRoot(rootEl).
If rootEl is null, handle gracefully (e.g., log an error via console.error or a
logger and return/abort) instead of passing the non-null assertion to
createRoot(render <App />), keeping the rest (StrictMode, App) unchanged.
In `@kaizen/frontend/ui/tsconfig.app.json`:
- Around line 12-16: Remove test-only type entries from the app TypeScript
config: delete "vitest/globals" and "@testing-library/jest-dom" from the "types"
array in tsconfig.app.json, and instead create a dedicated tsconfig.vitest.json
that extends the app config and adds those two entries to its "types" array (or
configure them via vite.config.ts test.globals). Update your test runner to use
tsconfig.vitest.json so test globals like describe/it/expect and jest-dom
matchers are only visible in test files.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
kaizen/frontend/ui/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (27)
.github/workflows/check-code.yamlMANIFEST.inREADME.mddocs/integrations/roo-code/Kaizen-export.yamldocs/integrations/roo-code/SETUP_ROO_MODE.mdkaizen/backend/milvus.pykaizen/frontend/api/__init__.pykaizen/frontend/api/routes.pykaizen/frontend/mcp/__main__.pykaizen/frontend/mcp/mcp_server.pykaizen/frontend/ui/.gitignorekaizen/frontend/ui/eslint.config.jskaizen/frontend/ui/index.htmlkaizen/frontend/ui/package.jsonkaizen/frontend/ui/setupTests.tskaizen/frontend/ui/src/App.tsxkaizen/frontend/ui/src/components/Dashboard.tsxkaizen/frontend/ui/src/components/EntityExplorer.test.tsxkaizen/frontend/ui/src/components/EntityExplorer.tsxkaizen/frontend/ui/src/components/Namespaces.test.tsxkaizen/frontend/ui/src/components/Namespaces.tsxkaizen/frontend/ui/src/index.csskaizen/frontend/ui/src/main.tsxkaizen/frontend/ui/tsconfig.app.jsonkaizen/frontend/ui/tsconfig.jsonkaizen/frontend/ui/tsconfig.node.jsonkaizen/frontend/ui/vite.config.ts
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (7)
kaizen/frontend/ui/src/components/EntityTable.tsx (1)
3-9: Entity type is defined locally — consider a shared types file.The same
Entityshape is consumed byEntityDetailModal,EntityExplorer, and this component. A single sharedtypes.tswould be a more maintainable home for it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/EntityTable.tsx` around lines 3 - 9, The Entity interface is duplicated locally; extract the shared shape into a single types file and import it where needed: create or update a shared types.ts exporting interface Entity { id: string; type: string; content: string; created_at?: string; metadata: Record<string, any> }, replace the local Entity declaration in EntityTable.tsx with an import, and update other consumers (EntityDetailModal, EntityExplorer) to import from that shared types.ts so the shape is maintained in one place.kaizen/frontend/ui/src/index.css (1)
333-364:.form-inputand.form-controlare near-identical.Both classes define the same background, border, radius, color, and focus styles. Consider consolidating into a single class (or having one extend the other) to avoid future drift.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/index.css` around lines 333 - 364, .form-input and .form-control duplicate most styles; consolidate them by extracting the shared properties (background, border, border-radius, color, font-family, width, padding, transition and focus border-color/outline) into a single base class (e.g., .form-field or .form-input) and have the other selector reuse it (either by replacing duplicated declarations with the shared class on elements or using a combined selector like .form-field, .form-control for common rules), keeping only the unique property (font-size: 0.95rem) in .form-control if needed; update the focus rules to target the shared class so focus behavior remains consistent.kaizen/frontend/ui/src/components/CreateEntityModal.tsx (2)
275-276: Using array index askeyfor dynamically added/removed trigger cards.When triggers are added or removed, index-based keys cause React to misassociate DOM state (e.g. input values) with the wrong trigger. Consider assigning a stable unique id to each trigger upon creation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 275 - 276, The map over policyTriggers in CreateEntityModal uses the array index as the React key which causes DOM/state misassociation when triggers are added/removed; change the trigger objects to include a stable unique id when they are created (e.g., in the add/create handler that pushes new triggers into policyTriggers) and then use that id as the key in the map (replace key={idx} with key={t.id}). Ensure any places that construct new trigger objects (e.g., defaultTrigger creation or addTrigger function) populate the id (UUID/timestamp/monotonic counter) so keys remain stable across reorders.
47-51:valparameter typed asany.Consider using the union of actual field types (
string | number | string[]) for stronger type safety instead ofany.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 47 - 51, The updateTrigger function currently types the val parameter as any; change its signature to use a stricter union type (e.g., val: string | number | string[]) and ensure the policyTriggers state and its item type reflect the corresponding field types so TypeScript can validate assignments; update any call sites to satisfy the new union and, if needed, refine the trigger item interface/type used by setPolicyTriggers and policyTriggers to match the allowed field shapes.kaizen/backend/milvus.py (1)
80-84: Prefer lazy%-formatting over f-strings in logger calls.
logger.exceptionalready appends the full traceback, so interpolating{e}into the message is redundant. Additionally, f-strings in logging calls bypass lazy formatting — Ruff ruleG004flags this pattern.Proposed fix
try: namespace.num_entities = self.milvus.get_collection_stats(namespace_id)["row_count"] except Exception as e: - logger.exception(f"Failed to get collection stats for namespace {namespace_id}: {e}") + logger.exception("Failed to get collection stats for namespace %s", namespace_id) namespace.num_entities = 0Same at Line 94:
- logger.exception(f"Failed to get collection stats for namespace {namespace.id}: {e}") + logger.exception("Failed to get collection stats for namespace %s", namespace.id)As per coding guidelines:
**/*.py: Use Ruff for linting and formatting (configured in pyproject.toml).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/backend/milvus.py` around lines 80 - 84, Replace the f-string interpolation in the logger.exception call around the self.milvus.get_collection_stats(...) block: stop embedding the exception into an f-string and instead use lazy % formatting like logger.exception("Failed to get collection stats for namespace %s", namespace_id) so logging remains lazy and Ruff G004 is satisfied; do the same fix for the other occurrence (the similar logger.exception at the second occurrence near the namespace.num_entities assignment) and keep the exception traceback provided by logger.exception itself.kaizen/frontend/ui/src/hooks/useApi.ts (1)
19-33: No request cancellation on URL change — stale responses can overwrite current data.If
urlchanges while a fetch is in-flight, the old response can resolve after the new one and overwritedatawith stale results. Using anAbortControllerin theuseEffectcleanup would prevent this.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/hooks/useApi.ts` around lines 19 - 33, The refetch callback can let stale fetches overwrite state when url changes; update refetch to use an AbortController: store the current controller in a ref (e.g., fetchControllerRef), abort the previous controller before creating a new one, pass the new controller.signal into fetch(url), and in the .catch ignore AbortError (do not set error) so aborted requests don't update state; also ensure you abort the controller on unmount (useEffect cleanup) and only call setLoading/setData/setError when the request was not aborted.kaizen/frontend/ui/src/components/EntityExplorer.test.tsx (1)
27-71: Consider adding error-state and delete-flow tests.The current tests cover loading, rendering, and modal opening — but there's no coverage for the error state (fetch failure → retry), the delete confirmation flow, or the type filter. These are key user paths in EntityExplorer.
Based on learnings: "All new features need tests (unit + e2e where applicable)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/EntityExplorer.test.tsx` around lines 27 - 71, Add unit tests in EntityExplorer.test.tsx to cover the missing error-state, delete flow, and type filter: for the error-state test, mock globalThis.fetch to reject or return ok: false, renderWithRouter(), assert the error UI (e.g., an error message and a retry button), then mock fetch to succeed on retry and assert the list re-renders; for the delete-flow test, render with mockEntities, locate the delete button for an entity, trigger click via userEvent, confirm the delete in the confirmation modal, assert that a DELETE request was sent (mockResolvedValue for that fetch) and that the entity is removed from the DOM; for the type-filter test, render with several typed entities, interact with the filter control (e.g., select or button) using userEvent, and assert only matching entity types are shown. Ensure you reference existing helpers and elements used in the file (renderWithRouter, userEvent.setup, screen.getByText/getByTestId) and reuse the same fetch mocking patterns as in other tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 148-160: The sort key is type-unsafe because x.get("created_at")
can be Any|None; change the lambda passed to result.sort to explicitly
narrow/convert created_at to a str (e.g. call datetime.isoformat earlier or use
a runtime check) so the key always returns a string; replace
result.sort(key=lambda x: x.get("created_at") or "", reverse=True) with
something like result.sort(key=lambda x: x.get("created_at") if
isinstance(x.get("created_at"), str) else "", reverse=True) or explicitly format
the created_at field to a string when building each item (in the dict creation)
so the sort key is guaranteed to be a str.
- Around line 194-206: The import of kaizen.schema.policy in routes.py breaks
mypy; add a local suppression and CI exception: annotate the import line "from
kaizen.schema.policy import Policy" with "# type: ignore[import-not-found]" to
silence static import errors, add a mypy override in pyproject.toml setting
ignore_missing_imports = true for the "kaizen.schema.policy" module, and (if
feasible) create the actual kaizen/schema/policy.py module exporting Policy so
runtime and type-checks can both succeed.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 116-124: The current fetch error handling in CreateEntityModal.tsx
calls await res.json() unconditionally which can throw on non-JSON error bodies;
update the logic in the create/fetch handler (the block that checks if (!res.ok)
then calls res.json()) to safely extract an error message by first attempting to
parse JSON in a try/catch, falling back to await res.text() if JSON parsing
fails, and include res.status/res.statusText with the parsed/fallback body when
throwing the Error so setCreateError receives a meaningful message; keep calling
onCreated() on success and ensure the thrown Error flows to the existing catch
that calls setCreateError(err.message).
- Around line 253-260: The onChange currently uses parseInt(e.target.value) ||
50 which treats 0 as falsy and resets it to 50; update the handler in
CreateEntityModal (the input using policyPriority and setPolicyPriority) to
parse with a radix and explicitly handle NaN instead of relying on || — e.g.
compute const v = parseInt(e.target.value, 10) and call
setPolicyPriority(Number.isNaN(v) ? 50 : v) so zero is preserved and
non-numeric/empty input falls back to 50.
In `@kaizen/frontend/ui/src/components/EntityDetailModal.tsx`:
- Around line 27-32: EntityDetailModal currently renders entity.content directly
which prints [object Object] for non-string content; update the rendering in the
detail-field-value block (in the EntityDetailModal component) to detect
non-string content (e.g., typeof entity.content !== 'string' or Array.isArray)
and render JSON.stringify(entity.content, null, 2) (or a safe string conversion)
so objects/arrays display readable JSON while preserving existing string
rendering.
- Around line 34-41: The code uses Object.keys(entity.metadata) which will throw
if entity.metadata is null/undefined; update the render guard in
EntityDetailModal to check metadata is present and an object before calling
Object.keys — e.g. replace the condition with a safe check such as
entity.metadata && typeof entity.metadata === 'object' &&
Object.keys(entity.metadata).length > 0 (or use Boolean(entity.metadata) &&
Object.keys(... || {}).length > 0) so the metadata block only renders when
metadata is non-null and iterable; keep the JSON.stringify call on
entity.metadata unchanged.
In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx`:
- Around line 13-25: The component calls useApi with apiUrl derived from
useParams id, and when id is missing apiUrl is null which causes useApi to stay
loading; add an early guard that checks the id before initiating the fetch: if
id is falsy, return early from the EntityExplorer component (render a
placeholder, error message, or null) so useApi is never invoked with a null
apiUrl. Update logic around id, apiUrl and the useApi call (references:
useParams id, apiUrl, useApi<Entity[]>) to ensure the fetch only runs when id is
present.
In `@kaizen/frontend/ui/src/components/EntityTable.tsx`:
- Around line 42-45: Update the Entity interface and render logic to handle
non-string content: change the Entity type for "content" from string to "string
| object" (or "string | any") in EntityTable.tsx (or the module that declares
Entity) and in the render where "ent.content" is used, add a defensive runtime
check that only calls substring on strings (e.g., typeof ent.content ===
'string'); for non-strings, safely convert to a preview string (JSON.stringify
with truncation or display a placeholder) before slicing so that
ent.content.substring(...) cannot be invoked on arrays/objects.
In `@kaizen/frontend/ui/src/hooks/useApi.ts`:
- Around line 14-20: The hook useApi initializes loading to true and never
clears it when url is null; update useApi so loading is initialized from the url
(e.g., set initial loading to !!url) and/or ensure refetch handles the null case
by calling setLoading(false) before returning; locate the useApi function and
adjust the initial useState for loading and the refetch function (and any
useEffect that triggers fetch) to explicitly setLoading(false) when url is null
so consumers do not stay stuck in a loading state.
In `@kaizen/frontend/ui/src/index.css`:
- Around line 22-29: Remove the unnecessary quotes around the Inter font in the
CSS rules (e.g., in the body selector and the other two places where 'Inter' is
used) so the font-family reads Inter, sans-serif; update each occurrence (the
body rule and the other selectors that currently use 'Inter') to drop the single
quotes, preserving the rest of the declaration exactly the same.
---
Nitpick comments:
In `@kaizen/backend/milvus.py`:
- Around line 80-84: Replace the f-string interpolation in the logger.exception
call around the self.milvus.get_collection_stats(...) block: stop embedding the
exception into an f-string and instead use lazy % formatting like
logger.exception("Failed to get collection stats for namespace %s",
namespace_id) so logging remains lazy and Ruff G004 is satisfied; do the same
fix for the other occurrence (the similar logger.exception at the second
occurrence near the namespace.num_entities assignment) and keep the exception
traceback provided by logger.exception itself.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 275-276: The map over policyTriggers in CreateEntityModal uses the
array index as the React key which causes DOM/state misassociation when triggers
are added/removed; change the trigger objects to include a stable unique id when
they are created (e.g., in the add/create handler that pushes new triggers into
policyTriggers) and then use that id as the key in the map (replace key={idx}
with key={t.id}). Ensure any places that construct new trigger objects (e.g.,
defaultTrigger creation or addTrigger function) populate the id
(UUID/timestamp/monotonic counter) so keys remain stable across reorders.
- Around line 47-51: The updateTrigger function currently types the val
parameter as any; change its signature to use a stricter union type (e.g., val:
string | number | string[]) and ensure the policyTriggers state and its item
type reflect the corresponding field types so TypeScript can validate
assignments; update any call sites to satisfy the new union and, if needed,
refine the trigger item interface/type used by setPolicyTriggers and
policyTriggers to match the allowed field shapes.
In `@kaizen/frontend/ui/src/components/EntityExplorer.test.tsx`:
- Around line 27-71: Add unit tests in EntityExplorer.test.tsx to cover the
missing error-state, delete flow, and type filter: for the error-state test,
mock globalThis.fetch to reject or return ok: false, renderWithRouter(), assert
the error UI (e.g., an error message and a retry button), then mock fetch to
succeed on retry and assert the list re-renders; for the delete-flow test,
render with mockEntities, locate the delete button for an entity, trigger click
via userEvent, confirm the delete in the confirmation modal, assert that a
DELETE request was sent (mockResolvedValue for that fetch) and that the entity
is removed from the DOM; for the type-filter test, render with several typed
entities, interact with the filter control (e.g., select or button) using
userEvent, and assert only matching entity types are shown. Ensure you reference
existing helpers and elements used in the file (renderWithRouter,
userEvent.setup, screen.getByText/getByTestId) and reuse the same fetch mocking
patterns as in other tests.
In `@kaizen/frontend/ui/src/components/EntityTable.tsx`:
- Around line 3-9: The Entity interface is duplicated locally; extract the
shared shape into a single types file and import it where needed: create or
update a shared types.ts exporting interface Entity { id: string; type: string;
content: string; created_at?: string; metadata: Record<string, any> }, replace
the local Entity declaration in EntityTable.tsx with an import, and update other
consumers (EntityDetailModal, EntityExplorer) to import from that shared
types.ts so the shape is maintained in one place.
In `@kaizen/frontend/ui/src/hooks/useApi.ts`:
- Around line 19-33: The refetch callback can let stale fetches overwrite state
when url changes; update refetch to use an AbortController: store the current
controller in a ref (e.g., fetchControllerRef), abort the previous controller
before creating a new one, pass the new controller.signal into fetch(url), and
in the .catch ignore AbortError (do not set error) so aborted requests don't
update state; also ensure you abort the controller on unmount (useEffect
cleanup) and only call setLoading/setData/setError when the request was not
aborted.
In `@kaizen/frontend/ui/src/index.css`:
- Around line 333-364: .form-input and .form-control duplicate most styles;
consolidate them by extracting the shared properties (background, border,
border-radius, color, font-family, width, padding, transition and focus
border-color/outline) into a single base class (e.g., .form-field or
.form-input) and have the other selector reuse it (either by replacing
duplicated declarations with the shared class on elements or using a combined
selector like .form-field, .form-control for common rules), keeping only the
unique property (font-size: 0.95rem) in .form-control if needed; update the
focus rules to target the shared class so focus behavior remains consistent.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
README.mdkaizen/backend/milvus.pykaizen/frontend/api/routes.pykaizen/frontend/mcp/mcp_server.pykaizen/frontend/ui/index.htmlkaizen/frontend/ui/src/components/ConfirmDialog.tsxkaizen/frontend/ui/src/components/CreateEntityModal.tsxkaizen/frontend/ui/src/components/Dashboard.tsxkaizen/frontend/ui/src/components/EntityDetailModal.tsxkaizen/frontend/ui/src/components/EntityExplorer.test.tsxkaizen/frontend/ui/src/components/EntityExplorer.tsxkaizen/frontend/ui/src/components/EntityTable.tsxkaizen/frontend/ui/src/components/Namespaces.tsxkaizen/frontend/ui/src/hooks/useApi.tskaizen/frontend/ui/src/hooks/useDebounce.tskaizen/frontend/ui/src/index.css
🚧 Files skipped from review as they are similar to previous changes (3)
- README.md
- kaizen/frontend/ui/index.html
- kaizen/frontend/ui/src/components/Namespaces.tsx
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
kaizen/frontend/api/routes.py (2)
194-206:⚠️ Potential issue | 🟠 Major
kaizen.schema.policymypy failure still unresolved.The
from kaizen.schema.policy import Policyinside thetry/except ImportErrorguard does not satisfy mypy's static analysis — mypy resolves imports statically and will emitCannot find implementation or library stub for module named "kaizen.schema.policy", failing theuv run mypy .CI step. The runtime fallback does not help the static checker.Fix options (as previously recommended):
- Add
# type: ignore[import-not-found]to the import line, or- Add a
[[tool.mypy.overrides]]entry inpyproject.tomlwithignore_missing_imports = trueforkaizen.schema.policy, or- Create the actual
kaizen/schema/policy.pymodule.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 194 - 206, The import of Policy inside the try/except in routes.py still fails mypy because imports are resolved statically; fix by making the import explicitly ignored by the type checker or by telling mypy to ignore the missing module: add a "# type: ignore[import-not-found]" to the "from kaizen.schema.policy import Policy" import, or add a mypy override in pyproject.toml for "kaizen.schema.policy" with ignore_missing_imports = true, or alternatively create the missing module kaizen/schema/policy.py that defines Policy so mypy can find it.
159-159: Sort key type-safety issue from the previous review is resolved.Line 159 now wraps with
str(x.get("created_at") or ""), guaranteeing astrreturn type and addressing the prior mypyIncompatible return value typepipeline error.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` at line 159, The sort key was causing a mypy type error; update the sort call in routes.py (the result.sort invocation) to coerce the key to a str by using key=lambda x: str(x.get("created_at") or ""), ensuring the key always returns a str and satisfies type-checking while preserving reverse chronological ordering; confirm this change is used where result is built so mypy error is resolved.
🧹 Nitpick comments (3)
kaizen/frontend/api/routes.py (3)
76-76: Sort key on line 76 lacksstr()wrapper — inconsistent with and potentially less type-safe than line 159.Line 159 correctly uses
str(x.get("created_at") or "")to guarantee astrreturn type, but line 76 omits thestr()cast. Mypy previously flagged the identical pattern at line 159; line 76 carries the same risk.♻️ Proposed fix
- recent_entities.sort(key=lambda x: x.get("created_at") or "", reverse=True) + recent_entities.sort(key=lambda x: str(x.get("created_at") or ""), reverse=True)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` at line 76, The sort on recent_entities uses a key lambda that returns x.get("created_at") or "" without guaranteeing a string; update the key to match the pattern used elsewhere (e.g., line 159) by wrapping the value in str(), so change the lambda in recent_entities.sort(...) to return str(x.get("created_at") or "") to ensure consistent, type-safe comparison.
109-131:HTTPExceptionshould be a top-level import, not re-imported inside everyexceptblock.
from fastapi import HTTPExceptionappears four times acrossadd_namespace,delete_namespace,delete_namespace_entity, andcreate_namespace_entity— always inside function or except scope. SinceHTTPExceptionis always needed, move it to the top-levelfastapiimport.♻️ Proposed refactor
-from fastapi import APIRouter, Query +from fastapi import APIRouter, HTTPException, QueryThen remove the repeated inline imports from each function/except block.
Also applies to: 165-175
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 109 - 131, The code repeatedly imports HTTPException inside function scopes (seen in add_namespace and delete_namespace and similarly in delete_namespace_entity and create_namespace_entity); move "from fastapi import HTTPException" to the module-level imports at the top of kaizen/frontend/api/routes.py and remove the inline imports from the except blocks in functions add_namespace, delete_namespace, delete_namespace_entity, and create_namespace_entity so they use the top-level HTTPException symbol instead.
14-17: PreferField(default_factory=dict)for the mutable default.
metadata: dict = {}works in Pydantic (it copies on instantiation), but the idiomatic approach is explicit:♻️ Proposed refactor
+from pydantic import BaseModel, Field + class EntityCreateRequest(BaseModel): type: str content: str - metadata: dict = {} + metadata: dict = Field(default_factory=dict)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 14 - 17, The EntityCreateRequest model uses a mutable default for metadata; change metadata: dict = {} to use Pydantic's Field with a default_factory to avoid mutable-default pitfalls: import Field from pydantic and update the metadata declaration on EntityCreateRequest to metadata: dict = Field(default_factory=dict) so each instance gets its own dict.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 93-100: This code reaches into client.backend.milvus internals
(client.backend.milvus.list_collections and
client.backend.milvus.get_collection_stats) which breaks the client abstraction
and can return None leading to AttributeError; instead add/use a client-level
method (e.g., Client.list_namespaces or Client.get_namespace_entity_count) to
obtain namespace names and counts, update the route to call that new API rather
than accessing client.backend.milvus directly, and ensure the client method
defensively handles None responses from get_collection_stats (returning 0 or
raising a clear error) so the route no longer swallows exceptions or
dereferences None.
- Around line 177-223: The function create_namespace_entity currently imports
HTTPException twice; remove the redundant "from fastapi import HTTPException"
inside the except block and rely on the earlier import at the top of the
function (the one at the start of create_namespace_entity). Ensure any exception
handling in the except block continues to raise HTTPException(status_code=400,
detail=str(e)) without re-importing the symbol.
In `@pyproject.toml`:
- Around line 89-90: Wrap the UI build in a subshell and purge stale artifacts
before running the build: replace the sequence with something like (cd
kaizen/frontend/ui && rm -rf dist && npm ci && npm run build) so the cwd is
isolated and restored on exit; then validate a specific, expected output file
(for example check kaizen/frontend/ui/dist/index.html exists and is non-empty)
rather than merely testing for a dist directory, and fail if the build command
exits non-zero. Ensure the existence check references the same path used inside
the subshell so stale artifacts cannot pass the check.
---
Duplicate comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 194-206: The import of Policy inside the try/except in routes.py
still fails mypy because imports are resolved statically; fix by making the
import explicitly ignored by the type checker or by telling mypy to ignore the
missing module: add a "# type: ignore[import-not-found]" to the "from
kaizen.schema.policy import Policy" import, or add a mypy override in
pyproject.toml for "kaizen.schema.policy" with ignore_missing_imports = true, or
alternatively create the missing module kaizen/schema/policy.py that defines
Policy so mypy can find it.
- Line 159: The sort key was causing a mypy type error; update the sort call in
routes.py (the result.sort invocation) to coerce the key to a str by using
key=lambda x: str(x.get("created_at") or ""), ensuring the key always returns a
str and satisfies type-checking while preserving reverse chronological ordering;
confirm this change is used where result is built so mypy error is resolved.
---
Nitpick comments:
In `@kaizen/frontend/api/routes.py`:
- Line 76: The sort on recent_entities uses a key lambda that returns
x.get("created_at") or "" without guaranteeing a string; update the key to match
the pattern used elsewhere (e.g., line 159) by wrapping the value in str(), so
change the lambda in recent_entities.sort(...) to return str(x.get("created_at")
or "") to ensure consistent, type-safe comparison.
- Around line 109-131: The code repeatedly imports HTTPException inside function
scopes (seen in add_namespace and delete_namespace and similarly in
delete_namespace_entity and create_namespace_entity); move "from fastapi import
HTTPException" to the module-level imports at the top of
kaizen/frontend/api/routes.py and remove the inline imports from the except
blocks in functions add_namespace, delete_namespace, delete_namespace_entity,
and create_namespace_entity so they use the top-level HTTPException symbol
instead.
- Around line 14-17: The EntityCreateRequest model uses a mutable default for
metadata; change metadata: dict = {} to use Pydantic's Field with a
default_factory to avoid mutable-default pitfalls: import Field from pydantic
and update the metadata declaration on EntityCreateRequest to metadata: dict =
Field(default_factory=dict) so each instance gets its own dict.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
kaizen/frontend/api/routes.pykaizen/frontend/ui/src/App.tsxkaizen/frontend/ui/src/components/Namespaces.test.tsxpyproject.toml
🚧 Files skipped from review as they are similar to previous changes (2)
- kaizen/frontend/ui/src/components/Namespaces.test.tsx
- kaizen/frontend/ui/src/App.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
kaizen/frontend/ui/src/components/CreateEntityModal.tsx (1)
332-336:⚠️ Potential issue | 🟡 MinorInconsistent trigger
valuetype — string vs string[] depending on comma presence.When the user types
"hello",valueis stored as a string. When they type"hello, world", it becomes["hello", "world"]. The backend receives different types for the same field, which can cause validation failures or inconsistent behavior.Always normalize to an array:
🐛 Proposed fix
onChange={(e) => { const val = e.target.value; - const arr = val.includes(',') ? val.split(',').map(s => s.trim()) : (val ? [val] : []); - updateTrigger(idx, 'value', val.includes(',') ? arr : val); + const arr = val.split(',').map(s => s.trim()).filter(Boolean); + updateTrigger(idx, 'value', arr); }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 332 - 336, The onChange handler for triggers in CreateEntityModal stores `value` as either a string or string[] depending on comma presence; change it to always normalize to an array by parsing the input (split on commas, trim each entry, filter out empty strings) and calling updateTrigger(idx, 'value', parsedArray) so the backend always receives a string[]; updateTrigger is the function to modify and the onChange handler in CreateEntityModal.tsx is the location to change.kaizen/frontend/api/routes.py (2)
190-235: RedundantHTTPExceptionimport on line 232 — already imported at line 194 in the same function.The second
from fastapi import HTTPExceptionin the outerexceptblock (line 232) is unnecessary since it's already imported at line 194 within the same function scope.Proposed fix
except Exception as e: - from fastapi import HTTPException - logger.error(f"Error creating entity in namespace {namespace_id}: {e}") raise HTTPException(status_code=400, detail=str(e))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 190 - 235, In create_namespace_entity, remove the redundant "from fastapi import HTTPException" inside the outer except block (the function already imports HTTPException near the top), leaving the except to simply log and raise HTTPException; this eliminates the duplicate import while keeping the error handling in the try/except around Entity creation and client.update_entities unchanged.
332-336: Inconsistent triggervaluetype — string vs string[] depending on comma presence.The
onChangehandler alternates between storing astringandstring[]forvaluebased on whether the input contains a comma. This creates an inconsistent payload shape sent to the backend, which may cause validation issues.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 332 - 336, The onChange handler currently stores `value` as either a string or string[] depending on comma presence, causing inconsistent payloads; change the logic in the onChange/handleTriggerChange path so `value` is normalized to a consistent type (preferably string[]) before any state set or API call: always split the input on commas, trim and filter empty entries to produce a string[] (or, if you choose string, join the array with commas at send time), and ensure the code paths that call setValue, validateTrigger, or sendTriggerPayload expect and handle that normalized type consistently.
🧹 Nitpick comments (5)
kaizen/frontend/api/routes.py (4)
139-144: Parametertypeshadows the Python built-in.The query parameter
typeon line 142 shadows the built-intype()function. While it won't cause issues in this narrow scope, it's a code smell that linters may flag.Proposed fix
def list_namespace_entities( namespace_id: str, - type: Optional[str] = Query(None, description="Filter entities by type (e.g., guideline, task)"), + entity_type: Optional[str] = Query(None, alias="type", description="Filter entities by type (e.g., guideline, task)"), limit: int = Query(100, description="Maximum number of entities to return"), ) -> List[dict[str, Any]]:Then update line 151:
- if type: - filters["type"] = type + if entity_type: + filters["type"] = entity_type🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 139 - 144, The query parameter name `type` in the function list_namespace_entities shadows Python's built-in type(); rename the parameter to something like entity_type (or type_) in the function signature and update all references inside list_namespace_entities (and any callers within the same module) to use entity_type instead of type so linters won't flag the shadowing and behavior remains identical.
109-121: Consider importingHTTPExceptionat the module level.
HTTPExceptionis lazily imported fromfastapiinsideexceptblocks across 4+ functions. SinceAPIRouterandQueryare already imported at the top level (line 5),HTTPExceptioncan be co-located there to reduce repetition and improve readability.Proposed fix
-from fastapi import APIRouter, Query +from fastapi import APIRouter, HTTPException, QueryThen remove all the inline
from fastapi import HTTPExceptionimports in the function bodies.Also applies to: 124-136, 175-187
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 109 - 121, Import HTTPException from fastapi at module level alongside existing top-level imports (where APIRouter and Query are imported) and remove the inline "from fastapi import HTTPException" statements inside functions such as add_namespace (and the other handlers referenced in the review at the other ranges). Update functions like add_namespace to use the top-level HTTPException symbol in their except blocks (and similarly for the handlers around lines 124-136 and 175-187) so there are no lazy in-function imports and the code is consistent and cleaner.
22-91: Dashboard still iterates all namespaces (up to 1000), fetching 10 entities each.While the per-namespace fetch was reduced from 1000 to 10 entities (addressing the earlier O(N×M) concern),
type_breakdownonly reflects the sampled 10 entities per namespace rather than the full distribution. Thetotal_entitiescount (line 52) comes fromns.num_entities, which is correct, but the type breakdown is misleading since it's based on a small sample. Worth a comment or a known-limitation note.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 22 - 91, The dashboard currently builds type_breakdown from the sampled entities fetched by get_all_entities (in get_dashboard), which misrepresents full distribution; rename the computed map to sampled_type_breakdown (or add a note field) and expose it along with a flag like type_breakdown_complete: False, and if namespace objects (namespaces / ns) expose aggregate stats (e.g., ns.type_counts or similar), merge or prefer those to produce a true type_breakdown and set the flag to True; update the response keys (type_breakdown -> sampled_type_breakdown, add type_breakdown_complete and optionally type_breakdown when full counts are available) and add a short inline comment in get_dashboard explaining the sampling limitation.
116-121: All error paths return HTTP 400 regardless of the actual error type.Namespace not found, permission errors, backend unavailable, and validation failures all map to 400 Bad Request. Consider differentiating — e.g., 404 for not-found, 500/503 for backend errors — to give the UI better error context.
Also applies to: 132-136, 184-187, 231-235
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 116 - 121, The current generic except Exception blocks in the route handlers (the block after the "return {"success": True, "namespace_id": req.namespace_id}" and the similar handlers at the other indicated ranges) always raise HTTP 400; change these to map real exception types to proper HTTP responses: catch and handle specific exceptions (e.g., NotFoundError -> raise HTTPException(status_code=404), PermissionError/AuthorizationError -> 403, ValidationError/RequestValidationError -> 422, backend connectivity errors (ConnectionError/TimeoutError) -> 503, and any unexpected exceptions -> 500) and preserve detailed logging (logger.error including the exception); you can implement a small helper like translate_exception_to_http(e) used in these route handlers to centralize the mapping and avoid repeating logic.kaizen/frontend/mcp/__main__.py (1)
7-13: Port 8000 is hardcoded with no feedback if binding fails.If port 8000 is already in use,
uvicorn.runwill throw and theexceptblock logs aterrorlevel — but sincelog_level="warning"is set on uvicorn and the main thread's stdio is consumed by MCP, the user may never see this. Consider logging a startup confirmation message or exposing the port via an environment variable.Suggested improvement
+import os + +API_PORT = int(os.environ.get("KAIZEN_UI_PORT", "8000")) + def run_api_server(): """Run the FastAPI server for UI and API in a background thread.""" try: - # We run with log_level="warning" to avoid cluttering stdio for MCP - uvicorn.run(app, host="127.0.0.1", port=8000, log_level="warning") + logging.info(f"Starting UI server on http://127.0.0.1:{API_PORT}/ui/") + uvicorn.run(app, host="127.0.0.1", port=API_PORT, log_level="warning") except Exception as e: logging.error(f"Failed to start UI server: {e}")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/mcp/__main__.py` around lines 7 - 13, The run_api_server function currently hardcodes port 8000 when calling uvicorn.run and only logs failures to logging.error (which may be missed); update run_api_server to read the port from an environment variable (e.g., MAVEN_MCP_PORT or MCP_API_PORT) with a sensible default, and log a startup confirmation including the chosen host and port before calling uvicorn.run; also ensure the except block logs the full exception details (include the exception object) so a caller can surface binding failures (reference run_api_server, uvicorn.run, and logging.error).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 116-126: The error handling for non-ok fetch responses in
CreateEntityModal.tsx consumes the response body by calling await res.json() and
then attempts await res.text(), which returns empty; fix by reading the body as
text first (e.g., const body = await res.text()) and then attempt to JSON-parse
that text (try JSON.parse(body) and extract .detail) before falling back to the
raw text; update the error construction around the res handling so the final
thrown Error includes either the parsed detail or the raw response text along
with the HTTP status.
In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx`:
- Around line 22-35: Hooks (useApi, useCallback, useEffect) must be moved above
the early return in EntityExplorer so they are invoked on every render; compute
apiUrl as null when id is falsy (e.g., const apiUrl = id ?
`/api/namespaces/${encodeURIComponent(id)}/entities${debouncedFilter ?
`?type=${encodeURIComponent(debouncedFilter)}` : ''}` : null) so useApi can skip
fetching on null, and update any useCallback/useEffect logic to safely handle a
null id (guard inside their bodies or include id in dependency arrays). Ensure
you reference EntityExplorer, useApi, useCallback, useEffect, apiUrl and
debouncedFilter when making these changes.
---
Duplicate comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 190-235: In create_namespace_entity, remove the redundant "from
fastapi import HTTPException" inside the outer except block (the function
already imports HTTPException near the top), leaving the except to simply log
and raise HTTPException; this eliminates the duplicate import while keeping the
error handling in the try/except around Entity creation and
client.update_entities unchanged.
- Around line 332-336: The onChange handler currently stores `value` as either a
string or string[] depending on comma presence, causing inconsistent payloads;
change the logic in the onChange/handleTriggerChange path so `value` is
normalized to a consistent type (preferably string[]) before any state set or
API call: always split the input on commas, trim and filter empty entries to
produce a string[] (or, if you choose string, join the array with commas at send
time), and ensure the code paths that call setValue, validateTrigger, or
sendTriggerPayload expect and handle that normalized type consistently.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 332-336: The onChange handler for triggers in CreateEntityModal
stores `value` as either a string or string[] depending on comma presence;
change it to always normalize to an array by parsing the input (split on commas,
trim each entry, filter out empty strings) and calling updateTrigger(idx,
'value', parsedArray) so the backend always receives a string[]; updateTrigger
is the function to modify and the onChange handler in CreateEntityModal.tsx is
the location to change.
---
Nitpick comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 139-144: The query parameter name `type` in the function
list_namespace_entities shadows Python's built-in type(); rename the parameter
to something like entity_type (or type_) in the function signature and update
all references inside list_namespace_entities (and any callers within the same
module) to use entity_type instead of type so linters won't flag the shadowing
and behavior remains identical.
- Around line 109-121: Import HTTPException from fastapi at module level
alongside existing top-level imports (where APIRouter and Query are imported)
and remove the inline "from fastapi import HTTPException" statements inside
functions such as add_namespace (and the other handlers referenced in the review
at the other ranges). Update functions like add_namespace to use the top-level
HTTPException symbol in their except blocks (and similarly for the handlers
around lines 124-136 and 175-187) so there are no lazy in-function imports and
the code is consistent and cleaner.
- Around line 22-91: The dashboard currently builds type_breakdown from the
sampled entities fetched by get_all_entities (in get_dashboard), which
misrepresents full distribution; rename the computed map to
sampled_type_breakdown (or add a note field) and expose it along with a flag
like type_breakdown_complete: False, and if namespace objects (namespaces / ns)
expose aggregate stats (e.g., ns.type_counts or similar), merge or prefer those
to produce a true type_breakdown and set the flag to True; update the response
keys (type_breakdown -> sampled_type_breakdown, add type_breakdown_complete and
optionally type_breakdown when full counts are available) and add a short inline
comment in get_dashboard explaining the sampling limitation.
- Around line 116-121: The current generic except Exception blocks in the route
handlers (the block after the "return {"success": True, "namespace_id":
req.namespace_id}" and the similar handlers at the other indicated ranges)
always raise HTTP 400; change these to map real exception types to proper HTTP
responses: catch and handle specific exceptions (e.g., NotFoundError -> raise
HTTPException(status_code=404), PermissionError/AuthorizationError -> 403,
ValidationError/RequestValidationError -> 422, backend connectivity errors
(ConnectionError/TimeoutError) -> 503, and any unexpected exceptions -> 500) and
preserve detailed logging (logger.error including the exception); you can
implement a small helper like translate_exception_to_http(e) used in these route
handlers to centralize the mapping and avoid repeating logic.
In `@kaizen/frontend/mcp/__main__.py`:
- Around line 7-13: The run_api_server function currently hardcodes port 8000
when calling uvicorn.run and only logs failures to logging.error (which may be
missed); update run_api_server to read the port from an environment variable
(e.g., MAVEN_MCP_PORT or MCP_API_PORT) with a sensible default, and log a
startup confirmation including the chosen host and port before calling
uvicorn.run; also ensure the except block logs the full exception details
(include the exception object) so a caller can surface binding failures
(reference run_api_server, uvicorn.run, and logging.error).
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
README.mdkaizen/frontend/api/routes.pykaizen/frontend/mcp/__main__.pykaizen/frontend/mcp/mcp_server.pykaizen/frontend/ui/README.mdkaizen/frontend/ui/src/components/CreateEntityModal.tsxkaizen/frontend/ui/src/components/EntityDetailModal.tsxkaizen/frontend/ui/src/components/EntityExplorer.tsxkaizen/frontend/ui/src/components/EntityTable.tsxkaizen/frontend/ui/src/hooks/useApi.tskaizen/frontend/ui/src/index.csspyproject.toml
✅ Files skipped from review due to trivial changes (1)
- kaizen/frontend/ui/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- kaizen/frontend/ui/src/hooks/useApi.ts
- kaizen/frontend/ui/src/components/EntityTable.tsx
- kaizen/frontend/mcp/mcp_server.py
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
kaizen/frontend/ui/src/components/CreateEntityModal.tsx (1)
332-336:⚠️ Potential issue | 🟠 MajorInconsistent
valuetype:stringvsstring[]depending on comma presence.When the input has no comma,
valueis stored as a plain string; with a comma, it becomesstring[]. This means the payload's triggervaluefield alternates types, which will likely cause backend validation issues or inconsistent behavior. Always normalize to an array.Proposed fix
onChange={(e) => { const val = e.target.value; - const arr = val.includes(',') ? val.split(',').map(s => s.trim()) : (val ? [val] : []); - updateTrigger(idx, 'value', val.includes(',') ? arr : val); + const arr = val.split(',').map(s => s.trim()).filter(Boolean); + updateTrigger(idx, 'value', arr); }},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 332 - 336, The onChange handler in CreateEntityModal currently sets trigger.value to a string when no comma is present and to string[] when commas exist; always normalize to an array to avoid alternating types: parse e.target.value into an array by splitting on commas, trimming items, filtering out empty strings, and using an empty array when the input is blank, then call updateTrigger(idx, 'value', parsedArray). Update the onChange (and any related code that assumes a string) to consistently treat trigger.value as string[].
🧹 Nitpick comments (7)
kaizen/frontend/ui/src/components/CreateEntityModal.tsx (2)
285-286: Using array index askeyfor a mutable list causes reconciliation bugs.Triggers can be added and removed, so using
idxas the React key means removing a middle trigger will cause subsequent items to incorrectly retain the state of their predecessor. Consider giving each trigger a stable identity (e.g., a counter-based id assigned on creation).Example approach
+ const [nextTriggerId, setNextTriggerId] = useState(1); + const [policyTriggers, setPolicyTriggers] = useState<(PolicyTrigger & { _key: number })[]>([ - { type: 'keyword', value: '', target: 'intent', operator: 'or', threshold: 0.7 } + { _key: 0, type: 'keyword', value: '', target: 'intent', operator: 'or', threshold: 0.7 } ]); const addTrigger = () => { - setPolicyTriggers([...policyTriggers, { type: 'keyword', value: '', target: 'intent', operator: 'or', threshold: 0.7 }]); + setPolicyTriggers([...policyTriggers, { _key: nextTriggerId, type: 'keyword', value: '', target: 'intent', operator: 'or', threshold: 0.7 }]); + setNextTriggerId(n => n + 1); };Then use
key={t._key}instead ofkey={idx}in the.map(), and strip_keybefore submitting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 285 - 286, The list rendering uses array index as the React key (policyTriggers.map((t, idx) => ... key={idx})), which breaks reconciliation when triggers are added/removed; instead assign a stable id to each trigger when created (e.g., add a _key field in the creation logic inside CreateEntityModal), render with key={t._key} on the .map() (the element with className "trigger-card"), and ensure you remove/strip the _key property before submitting/saving the payload so the backend does not receive the client-only id.
134-135: Modal backdrop doesn't support click-outside-to-dismiss.Other modals in this PR (e.g.,
ConfirmDialog,EntityDetailModal) attachonClick={onCancel/onClose}to the backdrop ande.stopPropagation()on the inner panel. This modal lacks both, so users cannot dismiss it by clicking outside. Consider adding the same pattern for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 134 - 135, CreateEntityModal's backdrop currently lacks a click-outside-to-dismiss behavior; update the JSX so the element with className "modal-backdrop" receives an onClick handler that calls the modal close prop (use onCancel or onClose depending on the component API) and ensure the inner panel element (e.g., the div rendering the modal content, often "modal-panel" or similar inside CreateEntityModal) calls e.stopPropagation() in its onClick to prevent closing when clicking inside. Also verify the prop name used by CreateEntityModal (onCancel/onClose) is present in the component props and wired to the backdrop handler.kaizen/frontend/api/routes.py (5)
118-118:HTTPExceptionis repeatedly imported insideexceptblocks — move it to the module-level import.
HTTPExceptionis imported fromfastapiinside theexcepthandler of three different endpoints (lines 118, 133, 184) plus once increate_namespace_entity(line 194). SinceAPIRouterandQueryare already imported fromfastapiat module level (line 5), there's no circular-dependency concern. A single top-level import is cleaner.Proposed fix
-from fastapi import APIRouter, Query +from fastapi import APIRouter, HTTPException, QueryThen remove the four local
from fastapi import HTTPExceptionstatements.Also applies to: 133-133, 184-184
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` at line 118, Add HTTPException to the module-level imports from fastapi (alongside APIRouter and Query) and remove the four local "from fastapi import HTTPException" statements inside the except handlers and inside create_namespace_entity; update the top import line to include HTTPException and delete the per-except imports so the endpoints and create_namespace_entity use the single top-level HTTPException symbol.
82-82: Sort keyx.get("created_at") or ""is inconsistent with line 168'sstr(x.get("created_at") or "").Line 168 wraps the sort key with
str()to satisfy type safety (per a past review fix), but this line uses the unwrapped form. Apply the same pattern for consistency and to avoid potential mypy complaints.Proposed fix
- recent_entities.sort(key=lambda x: x.get("created_at") or "", reverse=True) + recent_entities.sort(key=lambda x: str(x.get("created_at") or ""), reverse=True)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` at line 82, The sort key used in recent_entities.sort currently uses lambda x: x.get("created_at") or "" which is inconsistent with the type-safe pattern used elsewhere; change the lambda to wrap the value with str(...) like the other occurrence (e.g., use the same str(x.get("created_at") or "") pattern) so recent_entities.sort uses a string key and matches the implementation at the other occurrence.
94-106:list_namespacessilently returns[]on error — caller has no way to distinguish "no namespaces" from "backend failure".Same pattern in
list_namespace_entities(line 170-172). Consider returning an HTTP error status instead of an empty list so the UI can show an error state rather than "No namespaces found."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 94 - 106, The handlers list_namespaces and list_namespace_entities currently catch all exceptions and return an empty list, hiding backend errors; change them to log the exception and raise a FastAPI HTTPException (e.g., HTTPException(status_code=500, detail="Error fetching namespaces" or "Error fetching namespace entities")) so callers can distinguish failure from an empty result; import HTTPException (and optional status) from fastapi, keep the existing logger.error(e) call, and ensure the raised exception includes helpful context and the original error message.
16-19: Mutable default{}on a PydanticBaseModelfield — safe in v2, butField(default_factory=dict)is the idiomatic form.In Pydantic v2 this won't cause shared-state bugs (the default is copied), but tools like Ruff's
RUF012orB006may flag it. UsingField(default_factory=dict)makes the intent explicit.Proposed fix
+from pydantic import BaseModel, Field + class EntityCreateRequest(BaseModel): type: str content: str - metadata: dict = {} + metadata: dict = Field(default_factory=dict)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 16 - 19, The EntityCreateRequest model uses a mutable default for metadata (metadata: dict = {}); change it to use Pydantic's idiomatic factory by importing Field from pydantic and replacing the default with Field(default_factory=dict) so the metadata field becomes metadata: dict = Field(default_factory=dict); update the import if missing and keep the class name EntityCreateRequest unchanged.
208-221: Nestedtry/exceptswallowsHTTPExceptionif policy import succeeds but validation raises it.If
Policy(...)on line 215 raises a PydanticValidationError, the innerexcept Exception(line 216) catches it and re-raises asHTTPException(422). ThatHTTPExceptionthen propagates past the outerexcept ImportError(line 219) correctly — so the current flow works.However, the nesting is fragile: if any future code added between lines 210-218 raises an
ImportErrorfor an unrelated reason, the outerexcept ImportErrorwill silently swallow it. Consider restructuring to separate the import from the validation:Suggested restructure
elif req.type == "policy": + Policy = None try: from kaizen.schema.policy import Policy # type: ignore[import-not-found] - + except ImportError: + logger.warning("Policy schema missing. Skipping strict validation.") + + if Policy is not None: try: - # The Policy model checks the full payload policy_meta = {k: v for k, v in req.metadata.items() if k not in ("content", "type")} Policy(content=req.content, type=req.type, **policy_meta) except Exception as e: logger.error(f"Policy validation failed: {e}") raise HTTPException(status_code=422, detail=f"Invalid policy metadata schema: {e}") - except ImportError: - # Fallback if we're on a branch where kaizen.schema.policy doesn't exist yet - logger.warning("Policy schema missing. Skipping strict validation.")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 208 - 221, The code currently nests the Policy import and validation which lets an unrelated ImportError be swallowed; split the import of Policy (from kaizen.schema.policy) into its own try/except ImportError block and then perform validation separately: after a successful import, build policy_meta and call Policy(content=req.content, type=req.type, **policy_meta) inside a try that only catches pydantic.ValidationError (or narrow exceptions) and converts it to HTTPException(422) while logging, rather than using a broad except Exception; this ensures import failures are handled only by the import handler and validation errors are handled by the validation handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 46-83: The type_breakdown variable is being built from the
10-entity sample returned by client.get_all_entities(ns.id, limit=10) while
total_entities uses ns.num_entities, which is misleading; either compute an
accurate per-type count by calling a server-side count endpoint (e.g., use
client.get_entity_counts(ns.id) or a similar API per namespace) and aggregate
those into type_breakdown, or explicitly mark the sample as approximate (rename
to approximate_type_breakdown or add a boolean like type_breakdown_is_approx =
True) and adjust any response keys that expose type_breakdown; update the logic
around type_breakdown, the loop that currently uses client.get_all_entities, and
the response payload where recent_entities and type_breakdown are returned.
In `@kaizen/frontend/ui/src/components/EntityExplorer.tsx`:
- Around line 34-36: In handleDelete (EntityExplorer.tsx) change the error-body
handling to first read the response as text (await res.text()), then attempt to
parse JSON (try JSON.parse) falling back to the raw text if parse fails, and use
that result for the thrown Error message instead of directly calling await
res.json(); mirror the read-text-first pattern used in CreateEntityModal so
plain-text or HTML error bodies produce a meaningful error message rather than a
JSON parse SyntaxError.
---
Duplicate comments:
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 332-336: The onChange handler in CreateEntityModal currently sets
trigger.value to a string when no comma is present and to string[] when commas
exist; always normalize to an array to avoid alternating types: parse
e.target.value into an array by splitting on commas, trimming items, filtering
out empty strings, and using an empty array when the input is blank, then call
updateTrigger(idx, 'value', parsedArray). Update the onChange (and any related
code that assumes a string) to consistently treat trigger.value as string[].
---
Nitpick comments:
In `@kaizen/frontend/api/routes.py`:
- Line 118: Add HTTPException to the module-level imports from fastapi
(alongside APIRouter and Query) and remove the four local "from fastapi import
HTTPException" statements inside the except handlers and inside
create_namespace_entity; update the top import line to include HTTPException and
delete the per-except imports so the endpoints and create_namespace_entity use
the single top-level HTTPException symbol.
- Line 82: The sort key used in recent_entities.sort currently uses lambda x:
x.get("created_at") or "" which is inconsistent with the type-safe pattern used
elsewhere; change the lambda to wrap the value with str(...) like the other
occurrence (e.g., use the same str(x.get("created_at") or "") pattern) so
recent_entities.sort uses a string key and matches the implementation at the
other occurrence.
- Around line 94-106: The handlers list_namespaces and list_namespace_entities
currently catch all exceptions and return an empty list, hiding backend errors;
change them to log the exception and raise a FastAPI HTTPException (e.g.,
HTTPException(status_code=500, detail="Error fetching namespaces" or "Error
fetching namespace entities")) so callers can distinguish failure from an empty
result; import HTTPException (and optional status) from fastapi, keep the
existing logger.error(e) call, and ensure the raised exception includes helpful
context and the original error message.
- Around line 16-19: The EntityCreateRequest model uses a mutable default for
metadata (metadata: dict = {}); change it to use Pydantic's idiomatic factory by
importing Field from pydantic and replacing the default with
Field(default_factory=dict) so the metadata field becomes metadata: dict =
Field(default_factory=dict); update the import if missing and keep the class
name EntityCreateRequest unchanged.
- Around line 208-221: The code currently nests the Policy import and validation
which lets an unrelated ImportError be swallowed; split the import of Policy
(from kaizen.schema.policy) into its own try/except ImportError block and then
perform validation separately: after a successful import, build policy_meta and
call Policy(content=req.content, type=req.type, **policy_meta) inside a try that
only catches pydantic.ValidationError (or narrow exceptions) and converts it to
HTTPException(422) while logging, rather than using a broad except Exception;
this ensures import failures are handled only by the import handler and
validation errors are handled by the validation handler.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 285-286: The list rendering uses array index as the React key
(policyTriggers.map((t, idx) => ... key={idx})), which breaks reconciliation
when triggers are added/removed; instead assign a stable id to each trigger when
created (e.g., add a _key field in the creation logic inside CreateEntityModal),
render with key={t._key} on the .map() (the element with className
"trigger-card"), and ensure you remove/strip the _key property before
submitting/saving the payload so the backend does not receive the client-only
id.
- Around line 134-135: CreateEntityModal's backdrop currently lacks a
click-outside-to-dismiss behavior; update the JSX so the element with className
"modal-backdrop" receives an onClick handler that calls the modal close prop
(use onCancel or onClose depending on the component API) and ensure the inner
panel element (e.g., the div rendering the modal content, often "modal-panel" or
similar inside CreateEntityModal) calls e.stopPropagation() in its onClick to
prevent closing when clicking inside. Also verify the prop name used by
CreateEntityModal (onCancel/onClose) is present in the component props and wired
to the backdrop handler.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
kaizen/frontend/api/routes.pykaizen/frontend/ui/src/components/CreateEntityModal.tsxkaizen/frontend/ui/src/components/EntityExplorer.tsx
|
Ready to be reviewed. Includes files from PR Ready to be reviewed, so please merge that PR before |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
kaizen/frontend/ui/src/components/Dashboard.tsx (2)
30-30: Use hook refetch instead of full page reload for retry.At Line 47,
window.location.reload()is heavier than needed and resets SPA state. UserefetchfromuseApifor a scoped retry.Proposed change
-export default function Dashboard() { - const { data, loading, error } = useApi<DashboardData>('/api/dashboard'); +export default function Dashboard() { + const { data, loading, error, refetch } = useApi<DashboardData>('/api/dashboard'); ... - <button className="retry-btn" onClick={() => window.location.reload()}>Retry</button> + <button className="retry-btn" onClick={refetch}>Retry</button>Also applies to: 47-47
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/Dashboard.tsx` at line 30, The retry currently triggers a full page reload via window.location.reload(); instead call the refetch function provided by useApi to retry the dashboard request without resetting SPA state — locate the call site in Dashboard.tsx where useApi<DashboardData>('/api/dashboard') is used (symbols: useApi, DashboardData, data, loading, error) and replace the reload behavior with a call to the refetch() returned by the hook (or expose refetch from useApi if not yet returned) so the component re-issues the API request and re-renders without a full page refresh.
115-117: Prefer stable keys for pie cells.At Line 116, using
indexas key can cause reconciliation artifacts if ordering changes. Use a stable data-derived key.Proposed change
- {data.approximate_type_breakdown.map((_entry, index) => ( - <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> + {data.approximate_type_breakdown.map((entry, index) => ( + <Cell key={`cell-${entry.type}`} fill={COLORS[index % COLORS.length]} /> ))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/Dashboard.tsx` around lines 115 - 117, The pie chart cells are using a non-stable key (`cell-${index}`) in the map over data.approximate_type_breakdown which can cause reconciliation issues; update the key on the <Cell> inside the map to use a stable, data-derived identifier from each entry (e.g., a unique property like entry.id or entry.type) and only fallback to index if no stable property exists so the key becomes something like a composed stable identifier (e.g., `cell-${entry.id || entry.type || index}`) instead of always using the raw index.kaizen/frontend/mcp/mcp_server.py (1)
59-64: Consider namespacing the/assetsmount to avoid potential route conflicts.The
/assetspath is mounted at the root level, which could conflict with future API routes. Consider mounting it under/ui/assetsinstead to keep UI-related routes namespaced together.However, this would require adjusting the Vite build configuration to use a different base path, so this is a minor suggestion that can be deferred.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/mcp/mcp_server.py` around lines 59 - 64, Change the UI assets mount to a namespaced route to avoid conflicts: where assets_dir and ui_dist_dir are used and app.mount is invoked in mcp_server.py, replace the root-level mount path "/assets" with a namespaced path like "/ui/assets" (i.e., update the app.mount call that currently mounts StaticFiles(directory=assets_dir) to use "/ui/assets"); also ensure the secondary mount for ui_dist_dir ("/ui_static") remains as-is and note that Vite's base path may need to be updated if the UI expects assets at a different prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/ui/src/components/Dashboard.tsx`:
- Around line 41-47: In Dashboard component's error branch (the if (error ||
!data) block), ensure the displayed paragraph uses a fallback when error is
falsy: replace rendering of {error} with something like {error || "Unable to
load data. Please try again."} so when data is null but error is empty the user
sees a meaningful default message; keep the existing conditional and retry
button as-is.
---
Nitpick comments:
In `@kaizen/frontend/mcp/mcp_server.py`:
- Around line 59-64: Change the UI assets mount to a namespaced route to avoid
conflicts: where assets_dir and ui_dist_dir are used and app.mount is invoked in
mcp_server.py, replace the root-level mount path "/assets" with a namespaced
path like "/ui/assets" (i.e., update the app.mount call that currently mounts
StaticFiles(directory=assets_dir) to use "/ui/assets"); also ensure the
secondary mount for ui_dist_dir ("/ui_static") remains as-is and note that
Vite's base path may need to be updated if the UI expects assets at a different
prefix.
In `@kaizen/frontend/ui/src/components/Dashboard.tsx`:
- Line 30: The retry currently triggers a full page reload via
window.location.reload(); instead call the refetch function provided by useApi
to retry the dashboard request without resetting SPA state — locate the call
site in Dashboard.tsx where useApi<DashboardData>('/api/dashboard') is used
(symbols: useApi, DashboardData, data, loading, error) and replace the reload
behavior with a call to the refetch() returned by the hook (or expose refetch
from useApi if not yet returned) so the component re-issues the API request and
re-renders without a full page refresh.
- Around line 115-117: The pie chart cells are using a non-stable key
(`cell-${index}`) in the map over data.approximate_type_breakdown which can
cause reconciliation issues; update the key on the <Cell> inside the map to use
a stable, data-derived identifier from each entry (e.g., a unique property like
entry.id or entry.type) and only fallback to index if no stable property exists
so the key becomes something like a composed stable identifier (e.g.,
`cell-${entry.id || entry.type || index}`) instead of always using the raw
index.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
.secrets.baselineREADME.mdkaizen/frontend/api/routes.pykaizen/frontend/mcp/mcp_server.pykaizen/frontend/ui/src/components/Dashboard.tsxkaizen/frontend/ui/src/components/EntityExplorer.tsxpyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
- kaizen/frontend/api/routes.py
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (2)
kaizen/frontend/ui/src/components/Dashboard.tsx (1)
41-47:⚠️ Potential issue | 🟡 MinorShow a fallback message when
datais missing buterroris empty.The
error || !databranch can render a blank<p>because Line 46 prints only{error}.Proposed fix
- <p>{error}</p> + <p>{error || 'Unable to load dashboard data. Please try again.'}</p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/Dashboard.tsx` around lines 41 - 47, The conditional in Dashboard.tsx that returns the error state uses <p>{error}</p>, which can render empty when data is missing but error is falsy; update the JSX in the Dashboard component's error branch to show either the actual error message or a sensible fallback (e.g., "No data received" or "Unknown error") by rendering something like {error || 'No data received'} (or error?.toString() || 'No data received') instead of just {error}, so the UI always shows a helpful message when data is missing.kaizen/frontend/ui/src/components/CreateEntityModal.tsx (1)
332-336:⚠️ Potential issue | 🟡 MinorInconsistent trigger
valuetype — string vs string[] depending on comma presence.This is the same issue flagged in a past review for EntityExplorer. The value alternates between
stringandstring[]based on whether the user typed a comma, which may cause backend validation issues.🔧 Proposed fix — always normalize to array
onChange={(e) => { const val = e.target.value; - const arr = val.includes(',') ? val.split(',').map(s => s.trim()) : (val ? [val] : []); - updateTrigger(idx, 'value', val.includes(',') ? arr : val); + const arr = val.split(',').map(s => s.trim()).filter(Boolean); + updateTrigger(idx, 'value', arr); }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx` around lines 332 - 336, The onChange handler in CreateEntityModal.tsx currently sets trigger `value` to a string or string[] depending on commas; change it so updateTrigger(idx, 'value', ...) always receives an array: split on commas, trim entries, filter out empty strings, and for single non-comma input wrap it as a one-element array; update any references to trigger `value` (e.g., code that reads the value) to expect string[] where necessary and keep using the existing updateTrigger function to set the normalized array consistently.
🧹 Nitpick comments (3)
kaizen/frontend/ui/src/components/ConfirmDialog.tsx (1)
25-32: Unhandled rejection will leave loading indicator stuck ifonConfirmthrows.If the async
onConfirmthrows an error that isn't caught internally by the caller, thefinallyblock resetsloading, but the error propagates as an unhandled promise rejection. Consider wrapping in a try/catch that at least logs the error.Looking at the callers (
Namespaces.tsx:52-68,EntityExplorer.tsx:28-54), they catch errors internally, so this is currently safe. However, defensive error handling in the component itself would be more robust.🛡️ Optional defensive improvement
const handleConfirm = async () => { setLoading(true); try { await onConfirm(); + } catch (err) { + console.error('ConfirmDialog: onConfirm threw an error', err); } finally { setLoading(false); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/ConfirmDialog.tsx` around lines 25 - 32, The handleConfirm async should catch errors from await onConfirm() to avoid unhandled promise rejections; update handleConfirm to wrap the await onConfirm() call in a try/catch (keeping the existing finally that calls setLoading(false)), and in the catch log the error (e.g., console.error or the app logger) and then either rethrow or swallow based on desired behavior — reference handleConfirm, onConfirm, and setLoading to locate and modify the function.kaizen/frontend/ui/src/App.tsx (1)
31-46: Consider adding a catch-all route for unknown paths.Currently, navigating to an undefined route renders nothing in the main content area. A 404 fallback would improve UX.
🛡️ Proposed catch-all route
<Routes> <Route path="/" element={<Dashboard />} /> <Route path="/namespaces" element={<Namespaces />} /> <Route path="/namespaces/:id/entities" element={<EntityExplorer />} /> + <Route path="*" element={<Navigate to="/" replace />} /> </Routes>Note: This requires importing
Navigatefromreact-router-dom.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/App.tsx` around lines 31 - 46, Add a catch-all route to the App component's routing so unknown paths render a 404 fallback: update the Routes inside the App function to include a Route with path="*" that either renders a NotFound component (create one if missing) or redirects to an existing route using Navigate from react-router-dom (so also import Navigate). Specifically modify the Routes block that currently contains Route for "/", "/namespaces" and "/namespaces/:id/entities" to add the wildcard Route (path="*") referencing your NotFound component or Navigate to improve UX for unknown paths.kaizen/frontend/ui/src/index.css (1)
469-506: Consider addingprefers-reduced-motionmedia query for animations.The
pulseandspinanimations may cause discomfort for users with vestibular disorders. Wrapping them in a motion preference check improves accessibility.♿ Proposed accessibility improvement
+@media (prefers-reduced-motion: reduce) { + .status-dot, + .loader { + animation: none; + } +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/index.css` around lines 469 - 506, Add a prefers-reduced-motion media query to respect user motion preferences by disabling or reducing animations for the loader and any pulse animations: target the .loader class and the `@keyframes` spin (and any .pulse usage if present) so that inside `@media` (prefers-reduced-motion: reduce) you remove animation (e.g., animation: none) and avoid transforms/animated effects; update both the .loader rule and any pulse animation rules to fall back to a static state under that media query.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/mcp/__main__.py`:
- Around line 4-17: The module imports and calls uvicorn.run (uvicorn and
run_api_server in kaizen.frontend.mcp.__main__), but uvicorn is only a
transitive dependency; add uvicorn as a direct runtime dependency in
pyproject.toml (under [project] dependencies or the equivalent for your
packaging tool) and then regenerate the lockfile (e.g., poetry lock or
pip-tools/poetry update) so uvicorn is guaranteed at runtime; no code changes
required besides ensuring the package is listed and locked.
In `@kaizen/frontend/ui/src/components/ConfirmDialog.tsx`:
- Around line 4-12: ConfirmDialogProps declares onConfirm as () => void but
handleConfirm awaits onConfirm(), and callers pass async functions; update the
type of onConfirm in the ConfirmDialogProps interface to allow async functions
(e.g., () => Promise<void> or () => void | Promise<void>) so TypeScript
accurately reflects the contract; adjust any related usages or tests that assume
the old sync-only signature and keep onCancel as-is.
- Around line 53-60: The ConfirmDialog component currently shows a hardcoded
"Deleting..." when loading; update the component to accept an optional
loadingLabel prop (e.g., ConfirmDialogProps.loadingLabel) and use that when
loading, falling back to a derived label like `${confirmLabel}...` if
loadingLabel is not provided; change the button render (where handleConfirm is
used) to display loadingLabel || `${confirmLabel}...` instead of the hardcoded
string and update the prop types/defaults accordingly so non-delete actions
display correct loading text.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 86-93: The policy validation code is incorrectly nested inside the
guideline branch so policy requests bypass Pydantic validation; move the policy
validation block out of the if req.type == "guideline": block and place it in
its own branch (elif req.type == "policy":) at the same indentation level as the
guideline condition so that when req.type == "policy" the policy validation runs
before the Entity creation; adjust the control flow around the existing policy
validation logic and ensure the subsequent Entity creation path still uses the
validated data.
- Around line 304-308: The TriggerType options in the CreateEntityModal
component are incorrect; update the <select> options in CreateEntityModal.tsx
(the dropdown currently showing "regex" and "intent") to exactly match the
backend TriggerType enum values by replacing options with "keyword",
"natural_language", and "always" (use those values for the option value
attributes and user-facing labels), ensuring any form state or handlers that
read the selected value (e.g., the onChange or state variable used in
CreateEntityModal) continue to work with the new enum strings.
In `@kaizen/frontend/ui/src/components/Dashboard.tsx`:
- Around line 134-136: Recent entity keys use ent.id which can collide across
namespaces; update the list key in Dashboard.tsx where data.recent_entities.map
is used to use a namespace-scoped unique key (e.g. combine ent.namespace and
ent.id or use a stable unique field like ent.uid if available) instead of
key={ent.id} so React list reconciliation is stable across namespaces.
---
Duplicate comments:
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 332-336: The onChange handler in CreateEntityModal.tsx currently
sets trigger `value` to a string or string[] depending on commas; change it so
updateTrigger(idx, 'value', ...) always receives an array: split on commas, trim
entries, filter out empty strings, and for single non-comma input wrap it as a
one-element array; update any references to trigger `value` (e.g., code that
reads the value) to expect string[] where necessary and keep using the existing
updateTrigger function to set the normalized array consistently.
In `@kaizen/frontend/ui/src/components/Dashboard.tsx`:
- Around line 41-47: The conditional in Dashboard.tsx that returns the error
state uses <p>{error}</p>, which can render empty when data is missing but error
is falsy; update the JSX in the Dashboard component's error branch to show
either the actual error message or a sensible fallback (e.g., "No data received"
or "Unknown error") by rendering something like {error || 'No data received'}
(or error?.toString() || 'No data received') instead of just {error}, so the UI
always shows a helpful message when data is missing.
---
Nitpick comments:
In `@kaizen/frontend/ui/src/App.tsx`:
- Around line 31-46: Add a catch-all route to the App component's routing so
unknown paths render a 404 fallback: update the Routes inside the App function
to include a Route with path="*" that either renders a NotFound component
(create one if missing) or redirects to an existing route using Navigate from
react-router-dom (so also import Navigate). Specifically modify the Routes block
that currently contains Route for "/", "/namespaces" and
"/namespaces/:id/entities" to add the wildcard Route (path="*") referencing your
NotFound component or Navigate to improve UX for unknown paths.
In `@kaizen/frontend/ui/src/components/ConfirmDialog.tsx`:
- Around line 25-32: The handleConfirm async should catch errors from await
onConfirm() to avoid unhandled promise rejections; update handleConfirm to wrap
the await onConfirm() call in a try/catch (keeping the existing finally that
calls setLoading(false)), and in the catch log the error (e.g., console.error or
the app logger) and then either rethrow or swallow based on desired behavior —
reference handleConfirm, onConfirm, and setLoading to locate and modify the
function.
In `@kaizen/frontend/ui/src/index.css`:
- Around line 469-506: Add a prefers-reduced-motion media query to respect user
motion preferences by disabling or reducing animations for the loader and any
pulse animations: target the .loader class and the `@keyframes` spin (and any
.pulse usage if present) so that inside `@media` (prefers-reduced-motion: reduce)
you remove animation (e.g., animation: none) and avoid transforms/animated
effects; update both the .loader rule and any pulse animation rules to fall back
to a static state under that media query.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 90607b88-b6d3-4de2-8bad-379473b1e740
⛔ Files ignored due to path filters (1)
kaizen/frontend/ui/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (34)
.github/workflows/check-code.yaml.secrets.baselineMANIFEST.inREADME.mdkaizen/backend/milvus.pykaizen/frontend/api/__init__.pykaizen/frontend/api/routes.pykaizen/frontend/mcp/__main__.pykaizen/frontend/mcp/mcp_server.pykaizen/frontend/ui/.gitignorekaizen/frontend/ui/README.mdkaizen/frontend/ui/eslint.config.jskaizen/frontend/ui/index.htmlkaizen/frontend/ui/package.jsonkaizen/frontend/ui/setupTests.tskaizen/frontend/ui/src/App.tsxkaizen/frontend/ui/src/components/ConfirmDialog.tsxkaizen/frontend/ui/src/components/CreateEntityModal.tsxkaizen/frontend/ui/src/components/Dashboard.tsxkaizen/frontend/ui/src/components/EntityDetailModal.tsxkaizen/frontend/ui/src/components/EntityExplorer.test.tsxkaizen/frontend/ui/src/components/EntityExplorer.tsxkaizen/frontend/ui/src/components/EntityTable.tsxkaizen/frontend/ui/src/components/Namespaces.test.tsxkaizen/frontend/ui/src/components/Namespaces.tsxkaizen/frontend/ui/src/hooks/useApi.tskaizen/frontend/ui/src/hooks/useDebounce.tskaizen/frontend/ui/src/index.csskaizen/frontend/ui/src/main.tsxkaizen/frontend/ui/tsconfig.app.jsonkaizen/frontend/ui/tsconfig.jsonkaizen/frontend/ui/tsconfig.node.jsonkaizen/frontend/ui/vite.config.tspyproject.toml
🚧 Files skipped from review as they are similar to previous changes (20)
- .secrets.baseline
- kaizen/frontend/ui/src/components/Namespaces.test.tsx
- kaizen/frontend/ui/README.md
- kaizen/frontend/ui/src/components/EntityTable.tsx
- kaizen/frontend/ui/src/components/EntityExplorer.test.tsx
- kaizen/frontend/ui/index.html
- MANIFEST.in
- kaizen/frontend/ui/src/components/Namespaces.tsx
- kaizen/frontend/ui/.gitignore
- kaizen/frontend/ui/tsconfig.json
- kaizen/frontend/ui/src/hooks/useApi.ts
- kaizen/frontend/ui/tsconfig.node.json
- kaizen/frontend/ui/setupTests.ts
- kaizen/frontend/ui/src/main.tsx
- kaizen/frontend/ui/src/components/EntityDetailModal.tsx
- kaizen/frontend/ui/src/hooks/useDebounce.ts
- kaizen/frontend/ui/vite.config.ts
- kaizen/frontend/ui/tsconfig.app.json
- kaizen/frontend/api/routes.py
- kaizen/frontend/ui/eslint.config.js
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
kaizen/frontend/ui/src/components/Dashboard.tsx (1)
41-47:⚠️ Potential issue | 🟡 MinorRender a fallback message when
!datatrips this branch.
!datacan land here with a falsyerror, which leaves the error state blank above the Retry button.Suggested change
- <p>{error}</p> + <p>{error || 'Unable to load dashboard data.'}</p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/ui/src/components/Dashboard.tsx` around lines 41 - 47, In the Dashboard component, the conditional branch if (error || !data) can render an empty <p> when error is falsy but data is missing; update the rendering logic inside that branch so the paragraph displays a fallback message when error is undefined (e.g., use error || "No data available" or "Unable to load data" instead of {error}), ensuring the descriptive text appears under the "Connection Error" heading and the Retry button still reloads the page.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 100-107: The route currently swallows backend failures by logging
the exception and returning an empty list (see the client.all_namespaces loop
and the except block that calls logger.error and returns []); change this to
return an HTTP error response instead of a 200 with empty data — e.g.,
raise/return an appropriate framework-specific error (Flask abort(500,
...)/make_response or FastAPI HTTPException(status_code=500, detail=...))
including the logged error message so the frontend sees an error state; apply
the same change to the other similar catch block around lines 171-173 that also
logs and returns [].
- Around line 143-154: The public entities route currently accepts an unbounded
limit; clamp or validate the incoming limit parameter before calling
client.get_all_entities to prevent unbounded scans: add a MAX_LIMIT constant
(e.g., MAX_LIMIT = 500), sanitize the 'limit' parameter (ensure it's >=1 and set
limit = min(limit, MAX_LIMIT) or return a 400 for invalid values) inside the
handler that has the 'limit' param, then pass the sanitized limit into
get_all_entities(namespace_id, filters=filters, limit=limit); update any
docstring/Query description to mention the enforced maximum.
- Around line 209-215: The code constructs a Policy using
Policy(content=req.content, type=PolicyType(req.type), **policy_meta) but
req.type is just the entity kind ("policy"); replace that with the subtype from
req.metadata (e.g., req.metadata.get("policy_type")) when constructing
PolicyType, and adjust policy_meta extraction (currently removing "content" and
"type") so it does not drop "policy_type"; update the call to Policy to pass
PolicyType(req.metadata["policy_type"]) (or use .get with a clear
error/validation path) so PolicyType receives the actual policy subtype.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 4-10: The PolicyTrigger interface and form serialization must
produce backend-shaped values (list[string] | null) instead of raw ''/string and
should not force a dummy value for the 'always' type; update the PolicyTrigger
typing in CreateEntityModal.tsx and the trigger serialization logic used when
submitting/constructing triggers so that single scalar inputs are wrapped into
an array (e.g., ['value']) and empty/always triggers become null, and ensure the
same change is applied to the other trigger-handling spots referenced by the
component (the trigger creation/submit helpers and any map/filter that builds
the backend payload).
- Around line 95-102: The modal currently accepts any JSON (arrays, strings,
numbers, null) but the API expects metadata to be an object; in
CreateEntityModal update the validation after JSON.parse(newMetadata) (the
parsedMetadata variable) to verify it's a non-null plain object and not an array
(e.g., typeof parsedMetadata === 'object' && parsedMetadata !== null &&
!Array.isArray(parsedMetadata') ), and if that check fails call
setCreateError("Metadata must be a JSON object.") and return so the user sees
the client-side validation instead of a server 422.
---
Duplicate comments:
In `@kaizen/frontend/ui/src/components/Dashboard.tsx`:
- Around line 41-47: In the Dashboard component, the conditional branch if
(error || !data) can render an empty <p> when error is falsy but data is
missing; update the rendering logic inside that branch so the paragraph displays
a fallback message when error is undefined (e.g., use error || "No data
available" or "Unable to load data" instead of {error}), ensuring the
descriptive text appears under the "Connection Error" heading and the Retry
button still reloads the page.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 18877c9f-78e4-4a3a-8166-4ffb7bdb5de5
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
kaizen/frontend/api/routes.pykaizen/frontend/ui/src/components/ConfirmDialog.tsxkaizen/frontend/ui/src/components/CreateEntityModal.tsxkaizen/frontend/ui/src/components/Dashboard.tsxpyproject.toml
🚧 Files skipped from review as they are similar to previous changes (2)
- pyproject.toml
- kaizen/frontend/ui/src/components/ConfirmDialog.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
kaizen/frontend/api/routes.py (2)
82-85: Optional: Inconsistent sort key type handling.Line 84 uses
x.get("created_at") or ""while line 175 usesstr(x.get("created_at") or ""). For consistency and to satisfy strict type checkers, consider adding thestr()wrapper here as well.📝 Suggested fix
- recent_entities.sort(key=lambda x: x.get("created_at") or "", reverse=True) + recent_entities.sort(key=lambda x: str(x.get("created_at") or ""), reverse=True)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 82 - 85, The sort key for recent_entities uses lambda x: x.get("created_at") or "" which is inconsistent with other code that uses str(...); update the sort call (recent_entities.sort(...)) to wrap the value in str(...) — e.g., key=lambda x: str(x.get("created_at") or "") — so the sort key is always a string and matches the handling used elsewhere (e.g., the call at line with str(x.get("created_at") or "")).
219-225: Optional: Excludepolicy_typefrom the metadata dict.The
policy_metadict still containspolicy_typewhich gets passed to thePolicymodel as an extra field. While Pydantic ignores extra fields by default, it's cleaner to explicitly exclude it.📝 Suggested fix
- policy_meta = {k: v for k, v in req.metadata.items() if k != "content"} + policy_meta = {k: v for k, v in req.metadata.items() if k not in ("content", "policy_type")} Policy(content=req.content, type=PolicyType(req.metadata["policy_type"]), **policy_meta)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@kaizen/frontend/api/routes.py` around lines 219 - 225, policy_meta currently filters out "content" but still includes "policy_type", which is then passed as an extra field to Policy; update the metadata extraction so it excludes both "content" and "policy_type" (e.g., adjust the dict comprehension that builds policy_meta or remove the key before instantiating Policy) while keeping the existing use of Policy(content=req.content, type=PolicyType(req.metadata["policy_type"]), **policy_meta); reference symbols: policy_meta, req.metadata, Policy, PolicyType, req.content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 363-371: The label text next to the checkbox for the policy toggle
is awkward; update the label in CreateEntityModal (the input with id
"policyStatus" bound to policyEnabled and setPolicyEnabled) to a clearer string
such as "Policy Enabled" or simply "Enabled" by replacing "Policy Enabled active
state" with the chosen concise label so the UI reads naturally.
---
Nitpick comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 82-85: The sort key for recent_entities uses lambda x:
x.get("created_at") or "" which is inconsistent with other code that uses
str(...); update the sort call (recent_entities.sort(...)) to wrap the value in
str(...) — e.g., key=lambda x: str(x.get("created_at") or "") — so the sort key
is always a string and matches the handling used elsewhere (e.g., the call at
line with str(x.get("created_at") or "")).
- Around line 219-225: policy_meta currently filters out "content" but still
includes "policy_type", which is then passed as an extra field to Policy; update
the metadata extraction so it excludes both "content" and "policy_type" (e.g.,
adjust the dict comprehension that builds policy_meta or remove the key before
instantiating Policy) while keeping the existing use of
Policy(content=req.content, type=PolicyType(req.metadata["policy_type"]),
**policy_meta); reference symbols: policy_meta, req.metadata, Policy,
PolicyType, req.content.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8975a17a-497c-4481-a90a-4283574e03e5
📒 Files selected for processing (3)
kaizen/frontend/api/routes.pykaizen/frontend/ui/src/components/CreateEntityModal.tsxkaizen/frontend/ui/src/components/Dashboard.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- kaizen/frontend/ui/src/components/Dashboard.tsx
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
22-25:⚠️ Potential issue | 🟡 MinorAdd Node.js/npm to prerequisites for source UI builds.
The new source-build flow requires
npm, but prerequisites currently list only Python tooling. Please add a Node.js (and npm) requirement to prevent failed setup attempts.📝 Suggested doc patch
Prerequisites: - Python 3.12 or higher - `uv` (recommended) or `pip` +- Node.js 20+ and `npm` (required when building the UI from source)Also applies to: 45-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 22 - 25, Update the README prerequisites to include Node.js and npm (or a note that npm comes with Node.js) alongside Python and pip/uv; specifically add a line like "Node.js (>=16) and npm" in the prerequisites block you edited (the section currently showing "Python 3.12 or higher" and "`uv` (recommended) or `pip`") and duplicate the same addition in the other prerequisites block referenced (the later block around lines 45-50) so the source UI build instructions list Node/npm as required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@kaizen/frontend/api/routes.py`:
- Around line 107-112: The handlers currently log full exceptions via
logger.error but then raise HTTPException exposing the raw exception text (e.g.,
the block that logs via logger.error and raises HTTPException in the namespaces
fetch handler); change each raise HTTPException(...) to return a sanitized
client message like "Internal server error while fetching namespaces" (or
context-appropriate generic text) while keeping the original exception only in
the server log via logger.exception or logger.error with the exception object;
apply the same change to the other handlers noted (the blocks around lines
123-127, 138-142, 178-183, 193-198, 237-239) so no raw exception strings are
included in HTTPException.detail but are preserved in server logs for debugging.
- Around line 206-231: Normalize and validate req.type and req.content before
branching into schema checks: trim and lower-case req.type (e.g., use a
normalized variable before the if/elif) and ensure req.content is non-empty
(raise HTTPException 422 if invalid) so casing/whitespace can't bypass
validation; then use the normalized type to choose the Tip and Policy validation
paths (Tip, Policy, PolicyType) and continue to construct Entity(...) and call
get_client() only after passing these checks.
In `@kaizen/frontend/ui/src/components/CreateEntityModal.tsx`:
- Around line 82-97: The serializedTriggers mapping can produce non-'always'
triggers with value: null which still bypasses the initial validation; after
creating serializedTriggers (in the CreateEntityModal flow using policyName,
policyDesc, policyTriggers, serializedTriggers and setCreateError), add a
post-serialization guard that checks each trigger: if trigger.type !== 'always'
and (!trigger.value || (Array.isArray(trigger.value) && trigger.value.length ===
0)), call setCreateError("Policies require a name, description, and at least one
trigger with effective values.") and return to abort submit. Apply the same
post-serialization check to the other serialization block that mirrors this
logic elsewhere in CreateEntityModal.
---
Outside diff comments:
In `@README.md`:
- Around line 22-25: Update the README prerequisites to include Node.js and npm
(or a note that npm comes with Node.js) alongside Python and pip/uv;
specifically add a line like "Node.js (>=16) and npm" in the prerequisites block
you edited (the section currently showing "Python 3.12 or higher" and "`uv`
(recommended) or `pip`") and duplicate the same addition in the other
prerequisites block referenced (the later block around lines 45-50) so the
source UI build instructions list Node/npm as required.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: db893612-f69d-4e38-b617-efeb87419d68
📒 Files selected for processing (3)
README.mdkaizen/frontend/api/routes.pykaizen/frontend/ui/src/components/CreateEntityModal.tsx
…s & responsive design Bug Fixes: - Add missing .form-control, .retry-btn, .justify-end CSS classes High Priority: - Decompose EntityExplorer (612→145 lines) into EntityTable, EntityDetailModal, CreateEntityModal - Add useDebounce hook to prevent API flooding on filter keystrokes Medium Priority: - Extract 25+ inline styles into named CSS classes - Add ConfirmDialog component replacing native confirm()/alert() - Add responsive breakpoints for mobile (≤768px) - Add shared useApi hook, refactor Dashboard/Namespaces/EntityExplorer to use it
- milvus.py: use logger.exception for stacktrace preservation in get_collection_stats - routes.py: use ns.num_entities + limit=10 to prevent dashboard OOM; sanitize metadata dicts before unpacking in Tip/Policy constructors - mcp_server.py: add path traversal protection in serve_spa catch-all route - index.html: update title to Kaizen, replace broken vite.svg favicon - EntityExplorer.test.tsx: move globalThis.confirm stub into beforeEach - Namespaces.tsx: clear stale modal state on open/cancel - index.css: fix broken Google Fonts URL, add standard background-clip property
- Add kaizen/frontend/ui/README.md with React/Vite dev instructions - Update root README.md with UI build prerequisite warning - Resolve linter warnings via ruff
- kaizen/frontend/api/routes.py - kaizen/frontend/mcp/__main__.py - kaizen/frontend/mcp/mcp_server.py
…ard key reconciliation, and backend requirements
This commit addresses multiple findings:
- Added `uvicorn` as a direct runtime dependency in `pyproject.toml`
- Updated `ConfirmDialogProps` `onConfirm` to support async functions
- Allowed `ConfirmDialog` to accept a custom `loadingLabel`
- Fixed Pydantic policy validation block nesting in `/api/namespaces/{namespace_id}/entities`
- Matched `TriggerType` options in `CreateEntityModal` to the backend enum
- Fixed React list key un-stable reconciliation in `Dashboard.tsx` related to cross-namespace id collisions
This commit ensures that when the dashboard fails to load data but the error is empty or undefined, a meaningful error message 'Unable to load data. Please try again.' is displayed instead of a blank paragraph.
This change clamps the 'limit' parameter in the list_namespace_entities route to a maximum of 500 to prevent unbounded scans.
… routes This update changes list_namespaces and list_namespace_entities to raise a 500 HTTPException when the backend fails, ensuring the frontend can properly detect and display the error state.
|
@gaodan-fang @gjt-prog @illeatmyhat The tests have passed, can we please review this to merge? |
|
@visahak Can you address the coderabbit comments? Or say if they're not relevant. |
|
@vinodmut Can you check now. I addressed the issues. |
| @@ -100,6 +101,8 @@ build_command = ''' | |||
| perl -i -pe "s|\]\(docs/([^)]*)\)|](https://github.com/AgentToolkit/kaizen/blob/v$NEW_VERSION/docs/\$1)|g" README.md | |||
There was a problem hiding this comment.
wow, can't remember the last time i saw perl being used
This PR introduces a comprehensive React-based Web UI for Kaizen, served directly alongside the FastMCP backend.
Key Additions:
Guidelines,Policies) with specialized inputs like an array-mapped Trigger builder, preventing raw JSON syntax errors.uv run python -m kaizen.frontend.mcp) now boots both the MCP server and Web UI together.Testing:
MANIFEST.in.Summary by CodeRabbit