[Console Redesign / OBT-258] Reimplement the console UI on the Shema design - #40
Conversation
Removes the Organizations sidebar item and its Building2 icon import, the /app/organizations and /app/organizations/:orgId routes and their imports in App.tsx, and deletes the OrganizationsPage and OrganizationDetailPage components. Unknown organization paths now fall through to the NotFound (404) page. orgsAPI is kept in services/api.ts — it is still used by the project Access tab and the admin dashboard.
AppShell now denies the whole /app subtree to authenticated users who are neither platform admins nor managers, rendering the Access Denied page instead of the console content. Unauthenticated users are still redirected to /login and the loading state is unchanged. AccessDeniedPage gains a "logout" variant offering a Sign out action (instead of the Go to Dashboard link) so a fully denied user is not looped back into the gated shell.
…rm admins On the project Access tab, the per-user Role control is now interactive only for platform admins; other users (including managers) see the role as read-only text. Role changes still go through projectsAPI.updateUserRole, with the backend as the source of truth (US-11.9). Grant and revoke visibility is unchanged.
AuthContext now fetches the caller's managed project IDs (GET /api/auth/my-managed-projects) on login and session restore, exposes managedProjectIds, and derives isManager from managed projects or organizations. This matches the backend's per-project manager rule (US-11.6/11.8), so a user who manages projects but no organization is recognized as a manager and is no longer wrongly denied by the console shell gate (US-12.6) or hidden from the content navigation (US-12.2).
Replace the single `platform: string | null` field with `platforms: string[]` across AppResponse, AppCreate, AppUpdate and UserAppResponse to match the new multi-platform API schema (US-4.1). Keep the existing single-select forms working via a legacy<->array bridge at the API boundary; the multi-select UI (US-4.3) and per-platform badges (US-4.4) build on top of this change.
Replace the single Web/Mobile/Both dropdown in the app create and edit forms with a PlatformMultiSelect toggle group (Web, Android, iOS) that allows any combination. Require at least one platform before saving, pre-select the current platforms when editing, and send the selection as an array. Remove the transitional legacy<->array bridge introduced in US-4.2.
Show an individual badge for each entry in the app's platforms array on the dashboard app cards, the Manage Apps grid, and the app detail page, instead of a single combined label. Centralize the platform display labels in constants/platforms and drop the old platformLabel switch in AppCard.
Add the team_size field to ProjectResponse (US-9.1) and display it in the map popup's details grid with a people icon and a "N members" label, so users can gauge a project's scale at a glance.
Add a "New language" action next to the language selector that opens a nested dialog with name and code fields. On save it creates the language via the API, refreshes the languages store, and auto-selects the new entry — the project form state is preserved throughout. Duplicate-code errors are surfaced to the user.
Introduce a shared FilterBar with configurable filter dropdowns (label, options, value, change handler), an optional search input, and an optional result label. Active filters are highlighted with the telha accent, and the standalone Filter icon is dropped to reduce visual noise. Styled with the Shema design tokens.
Replace the inline search + app filter JSX with the shared FilterBar component, passing the existing search and app-filter state as props. Behavior is unchanged.
Replace the inline app + status filter JSX with the shared FilterBar component, passing filterApp and filterStatus as filter configs and the request count as the result label. Behavior is unchanged.
Remove the permanent right-hand dependency column. The phases page now uses a single full-width layout (graph + table), and selecting a phase — from the table row or a graph node — opens a dialog to manage its dependencies. The DependencyPanel drops its standalone card wrapper since it now lives in the modal.
Display the selected phase's description (read-only) above its dependency list, with a subtle "No description" placeholder when empty, so users get full context without opening the edit dialog.
Add an SVG <title> to each phase node so hovering over it reveals the phase description as a native browser tooltip, falling back to "No description" when empty. This does not interfere with clicking or dragging the graph.
Add an edit affordance (pencil on hover) to each language card that opens the shared dialog pre-filled with the language's name and code. Saving calls the new languagesAPI.update (PUT), refreshes the store, and surfaces duplicate-code (409) errors. Add the LanguageUpdate type and updated_at to LanguageResponse (US-1.1).
Add an admin-only deactivate affordance to each language card. Clicking it fetches
the language usage stats and shows the impact ("used in N projects") in a
confirmation dialog before soft-deleting via languagesAPI.delete. Add
languagesAPI.delete/stats, the LanguageStatsResponse type, and is_active on
LanguageResponse (US-2.1/2.2).
Add an "X/Y completed" summary next to the Phases heading in the map popup, computed from the phases already loaded for the project, so users can gauge overall progress at a glance.
Use a Globe icon for the web launch button (instead of a generic external-link), an Apple icon for iOS, and keep Smartphone for Android so the two mobile buttons are visually distinct and instantly recognizable.
Add a hover overlay over the image preview with clear icon actions — a camera to change and an X to remove — so users can discover the actions instead of missing the small text buttons. Works for both the square and circle shape variants and applies everywhere ImageUpload is used.
…-frontend-manager-model-to-per-project' into local/integration-test
…ript-types-to-reflect-the-new-platforms' into local/integration-test
…tiple-platforms-independently-web-android' into local/integration-test
…dual-platform-badges-for-each-app-eg-web' into local/integration-test
…-buttons-to-show-platform-specific-icons' into local/integration-test
…remove-actions-to-be-visually-obvious' into local/integration-test
…-well-designed-filter-bar-component' into local/integration-test
…-bar-to-look-polished-and-consistent' into local/integration-test
…-bar-to-look-polished-and-consistent' into local/integration-test
…ase-description-alongside-its-dependencies' into local/integration-test
Match the prototype's project card and detail header: - progress bar with "X/Y phases completed" from the enriched payload - up to four overlapping member avatars, photo or hashed initials - language code chip stays lowercase mono; "Updated Jul 8, 2026" - detail header: upload and remove a project image, and resolve the language directly when the store misses it, so it no longer reads "Unknown language" - split the info form and location card out of the page Refs OBT-262 Co-Authored-By: Claude Fable 5 <[email protected]>
- users: "Platform admin" label, solid role badges, legend dots that match them, "N app roles", and a count on the access-requests tab - project access: the role control reads as a pill — solid telha for manager, muted for member — and rows show real avatars - apps: tiles fall back to hashed initials instead of one flat olive - reviewing a request refreshes the pending counts, so the badges settle Co-Authored-By: Claude Fable 5 <[email protected]>
The user detail page sent folder=user-avatars, which is not in the API's allowlist, so admin-set photos were silently stored under the generic images/ prefix instead of alongside the ones ProfileDialog uploads. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
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:
📝 WalkthroughWalkthroughThe pull request adds public language/project requests with optional reCAPTCHA, introduces change-request review flows, expands role-aware administration, refactors app/project/user/dashboard/map/phase pages into reusable components, and replaces legacy styling with semantic light/dark design tokens. ChangesConsole foundation and shared UI
Public requests and reviews
Administration and management pages
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 22
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/pages/phases/PhaseFlowGraph.tsx (1)
334-375: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMake interactive phase nodes keyboard-accessible.
Both graph implementations expose essential actions through mouse-only elements:
src/components/pages/phases/PhaseFlowGraph.tsx#L334-L375: add focusability, button semantics, an accessible name, and Enter/Space handling to each SVG node.src/components/pages/ProjectPhasesTab.tsx#L184-L200: render the clickable phase card as a native button or provide equivalent keyboard behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/phases/PhaseFlowGraph.tsx` around lines 334 - 375, Make the phase node group rendered in PhaseFlowGraph.tsx keyboard-accessible by adding focusability, button semantics, an accessible name, and Enter/Space key handling that invokes handleNodeClick like the existing click handler. Also update the clickable phase card in ProjectPhasesTab.tsx to use a native button or equivalent keyboard interaction, preserving its current activation behavior.
🟡 Other comments (13)
src/components/pages/dashboard/NeedsReviewPanel.tsx-78-82 (1)
78-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not route “All requests” to the access-only destination.
This panel aggregates access, change, and public requests, but this link always opens
/app/users; change/public reviews live under projects or languages. Remove the link, relabel it “Access requests,” or route it to an actual combined review page.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/dashboard/NeedsReviewPanel.tsx` around lines 78 - 82, Update the “All requests” Link in the NeedsReviewPanel heading so it does not route to the access-only /app/users destination; either remove the link, relabel it “Access requests” to match that route, or point it to an existing combined review page covering access, change, and public requests.src/components/pages/dashboard/AdminStatsRow.tsx-19-34 (1)
19-34: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winLabel public requests explicitly.
changeCountincludespendingPublic, but the subtitle reports the entire value as “change,” producing incorrect category counts.Proposed fix
- subtitle={`${accessCount} access · ${changeCount} change`} + subtitle={`${accessCount} access · ${changeCount} change/public`}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/dashboard/AdminStatsRow.tsx` around lines 19 - 34, Update the pending reviews subtitle in AdminStatsRow so pendingPublic requests are reported as a distinct category instead of being included under the change count. Keep changeCount’s existing total contribution and add the corresponding public-request count to the displayed breakdown.src/components/pages/publicRequest/ProjectRequestForm.tsx-90-100 (1)
90-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the server’s 409 conflict detail.
The fixed language-conflict message can misreport duplicate project requests, especially when no new language is proposed. Use
detailwith a neutral fallback.Proposed fix
if (response?.status === 409) { - toast.error(detail || "This language already exists or was already requested") + toast.error(detail || "A matching project or language request already exists")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/publicRequest/ProjectRequestForm.tsx` around lines 90 - 100, Update the 409 handling in ProjectRequestForm to display the server-provided detail when available, with a neutral fallback message that covers any conflict rather than assuming a language duplicate. Leave the existing 429, 422, and 400 handling unchanged.src/components/common/FilterBar.tsx-42-47 (1)
42-47: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winAdd
aria-labelto the search input for screen reader accessibility.The search input relies solely on
placeholderfor identification, which is not a reliable accessible name. The adjacentSearchicon is decorative and not associated with the input. Add anaria-labelto ensure screen readers announce the input's purpose.♿ Proposed fix
<input + aria-label={search.placeholder ?? "Search"} placeholder={search.placeholder ?? "Search..."} value={search.value} onChange={(e) => search.onChange(e.target.value)} className="bg-transparent border-0 outline-none text-[13.5px] text-fg-strong placeholder:text-fg-subtle w-full" />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/common/FilterBar.tsx` around lines 42 - 47, Update the search input in FilterBar’s input rendering to include an explicit aria-label describing its search purpose, while preserving the existing placeholder, value, and onChange behavior.src/components/common/LocationSearchInput.tsx-128-134 (1)
128-134: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winGive the clear-location button an accessible name.
The icon-only button is announced without a purpose by assistive technology. Add
aria-label="Clear location".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/common/LocationSearchInput.tsx` around lines 128 - 134, Add aria-label="Clear location" to the icon-only button rendering the X icon in LocationSearchInput, while preserving its existing handleClear behavior and styling.src/components/common/UserAvatar.tsx-21-24 (1)
21-24: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReset the image-error state when
avatarUrlchanges.After one image fails, subsequent valid avatar URLs remain hidden until the component remounts.
Proposed fix
-import { useState } from "react" +import { useEffect, useState } from "react" export function UserAvatar({ id, name, email, avatarUrl, size = "md", className }: UserAvatarProps) { const [imgError, setImgError] = useState(false) + + useEffect(() => { + setImgError(false) + }, [avatarUrl])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/common/UserAvatar.tsx` around lines 21 - 24, Update UserAvatar so its imgError state resets whenever avatarUrl changes, allowing a newly provided URL to render after a previous image failure. Add the reset alongside the existing state logic while preserving the current showImage condition for unchanged URLs.src/components/pages/PhasesPage.tsx-213-220 (1)
213-220: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake selectable phase rows keyboard-accessible.
The click-only row cannot be focused or activated by keyboard users.
Proposed fix
<tr key={phase.id} + tabIndex={0} + aria-selected={selectedPhaseId === phase.id} className={cn( - "cursor-pointer transition-colors hover:bg-muted", + "cursor-pointer transition-colors hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-[-2px]", selectedPhaseId === phase.id && "bg-muted", )} onClick={() => setSelectedPhaseId(phase.id)} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault() + setSelectedPhaseId(phase.id) + } + }} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/PhasesPage.tsx` around lines 213 - 220, Make the phase rows rendered in the PhasesPage table keyboard-accessible by adding focusability and keyboard activation alongside the existing onClick handler. Update the row identified by key={phase.id} to respond to Enter and Space by selecting the phase, while preserving mouse selection and the existing visual styling.src/components/pages/userDetail/AccountCard.tsx-29-38 (1)
29-38: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winGive the account-status switch an accessible name.
The adjacent status text is not associated with the
Switch, leaving the control unlabeled for screen readers.Proposed fix
- <Switch checked={user.is_active} onCheckedChange={onToggleActive} /> + <Switch + checked={user.is_active} + onCheckedChange={onToggleActive} + aria-label="Account active" + />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/userDetail/AccountCard.tsx` around lines 29 - 38, The Switch in AccountCard needs an accessible name because the adjacent status text is not associated with it. Update the Switch usage in the account-status section to provide an accessible label, reusing the existing Active/Inactive status context without changing the onToggleActive behavior.src/components/pages/ProjectsPage.tsx-92-100 (1)
92-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the role-aware label in the empty state.
For managers, this action opens the request workflow, but it is labeled “Create Project.”
Proposed fix
- actionLabel="Create Project" + actionLabel={isPlatformAdmin ? "Create Project" : "Request Project"}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/ProjectsPage.tsx` around lines 92 - 100, Update the EmptyState action label in the projectsView empty-state branch to use the role-aware label that matches openCreateDialog’s workflow, showing the request-oriented text for managers while preserving the create-project label for other roles.src/components/layout/Sidebar.tsx-182-186 (1)
182-186: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFetch review counts for managers too.
Lines 198–205 render language and project badges for managers, but this effect only populates them for platform admins. Manager sessions therefore show empty or stale pending-review counts.
- if (isPlatformAdmin) { + if (isPlatformAdmin || isManager) { void fetchCounts() } - }, [isPlatformAdmin, fetchCounts, pathname]) + }, [isPlatformAdmin, isManager, fetchCounts, pathname])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/layout/Sidebar.tsx` around lines 182 - 186, Update the useEffect that invokes fetchCounts so it runs for both platform admins and managers, using the existing manager-access condition alongside isPlatformAdmin. Preserve the current dependency tracking and avoid restricting count fetching to platform-admin sessions.src/components/ui/button.tsx-11-17 (1)
11-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRestore destructive-action styling.
The shared destructive variant is identical to the primary variant, while app deletion explicitly uses the primary variant:
src/components/ui/button.tsx#L11-L17: givedestructivea distinct danger treatment.src/components/pages/apps/DangerZoneCard.tsx#L10-L17: usevariant="destructive"for “Delete app…”.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/ui/button.tsx` around lines 11 - 17, The shared destructive variant in src/components/ui/button.tsx lines 11-17 must use distinct danger styling instead of duplicating the default variant. Update the delete-app button usage in src/components/pages/apps/DangerZoneCard.tsx lines 10-17 to set variant="destructive", while preserving the existing button behavior.src/components/pages/userDetail/GlobalRoleCard.tsx-25-56 (1)
25-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExpose the selected role programmatically.
These custom radio-like buttons only render a visual indicator. Use a
radiogroupwitharia-checked, or addaria-pressed={selected}, so assistive technology can identify the current role.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/userDetail/GlobalRoleCard.tsx` around lines 25 - 56, Expose selection state in the roleChoices button controls by adding aria-pressed={selected} to each button, or alternatively implement a radiogroup with aria-checked. Preserve the existing selected styling and roleSaving behavior while ensuring assistive technology can identify the current role.src/components/pages/projectAccess/GrantUserDialog.tsx-61-76 (1)
61-76: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winProvide programmatic names for the newly introduced controls. The controls currently rely on visual labels, initials, or
title, which do not consistently expose their intended action.
src/components/pages/projectAccess/GrantUserDialog.tsx#L61-L76: associate the roleLabelandSelectTriggerusinghtmlForandid.src/components/pages/userDetail/UserHeader.tsx#L20-L42: add an explicit upload-photoaria-label.src/components/pages/apps/AutoApproveCard.tsx#L20-L24: associate a label or add an auto-approvearia-label.src/components/pages/ProjectDetailPage.tsx#L191-L200: add an explicit upload-project-imagearia-label.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/projectAccess/GrantUserDialog.tsx` around lines 61 - 76, Provide programmatic names for all newly introduced controls: in src/components/pages/projectAccess/GrantUserDialog.tsx lines 61-76, connect the role Label and SelectTrigger with matching htmlFor and id values; in src/components/pages/userDetail/UserHeader.tsx lines 20-42, add an explicit upload-photo aria-label; in src/components/pages/apps/AutoApproveCard.tsx lines 20-24, associate the control with its label or add an auto-approve aria-label; and in src/components/pages/ProjectDetailPage.tsx lines 191-200, add an explicit upload-project-image aria-label.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/App.tsx`:
- Line 55: Update the phases route in App.tsx to use an authorization wrapper
that permits users when isPlatformAdmin or isManager is true, preserving
platform-admin access while allowing managers to edit the global phase catalog;
only change the page documentation/copy instead if the intended contract is
admin-only.
In `@src/components/layout/Sidebar.tsx`:
- Around line 240-263: Update the mobile drawer rendered by the mobileOpen
branch in SidebarContent usage to provide modal dialog behavior: trap focus
within the open drawer, close it on Escape, restore focus to the triggering
element after closing, and expose appropriate dialog semantics. Prefer the
existing dialog/sheet primitive if available; otherwise implement equivalent
focus management while preserving the current onMobileClose behavior.
In `@src/components/pages/apps/AppCard.tsx`:
- Around line 26-31: Replace the non-focusable onClick container in AppCard with
a semantic link or button for opening the app, preserving its existing styling
and action. In src/components/pages/apps/AppCard.tsx lines 77-92, make
edit/delete controls visible on keyboard focus and devices without hover. In
src/components/pages/projects/ProjectCard.tsx lines 33-44, make project
navigation keyboard-operable and reveal the editing action on focus.
In `@src/components/pages/apps/DetailsCard.tsx`:
- Around line 65-72: Update the save-action validation in the form containing
the Platforms `PlatformMultiSelect` so it is disabled whenever `form.platforms`
is empty, enforcing the displayed minimum of one platform. Preserve the existing
save behavior when at least one platform is selected, including the
corresponding validation path noted near the alternate save control.
In `@src/components/pages/apps/RolesCard.tsx`:
- Around line 70-90: Update the role label and role_key inputs in RolesCard to
provide a visible keyboard focus indicator instead of only removing the outline.
Prefer replacing them with the shared Input component; otherwise add the
established focus-visible ring styling to both inputs while preserving their
existing behavior and appearance.
In `@src/components/pages/ChangeRequestsSection.tsx`:
- Around line 55-79: Update fetchRequests in ChangeRequestsSection to guard
against out-of-order responses by sequencing or cancelling each fetch, and only
apply results from the currently active request. Clear the existing requests
when the active fetch fails, including public-request loading failures as
appropriate, while preserving loading-state cleanup. Add useRef to the React
imports if using a sequence guard.
In `@src/components/pages/dashboard/MapPreview.tsx`:
- Around line 41-53: The MapPreview MapContainer currently disables Leaflet
attribution without providing a replacement. Restore visible basemap attribution
by enabling the attribution control or configuring equivalent attribution on the
TileLayer, using the existing tileUrl flow in MapContainer and TileLayer.
In `@src/components/pages/dashboard/useAdminDashboardData.ts`:
- Around line 39-47: Update the pending-request loading in useAdminDashboardData
so accessRequestsAPI, changeRequestsAPI, and publicRequestsAPI failures remain
distinguishable from successful empty responses. Preserve per-source error or
unknown state, and prevent the dashboard from reporting definitive zero pending
reviews until each source has loaded successfully.
In `@src/components/pages/DashboardPage.tsx`:
- Line 29: Update the data-loading flow in DashboardPage so rejected
Promise.allSettled results are inspected explicitly instead of treating them as
empty data through setApps([]). Expose the existing error/retry state for any
rejected API request, or replace allSettled with Promise.all if both requests
must succeed before rendering.
In `@src/components/pages/LanguagesPage.tsx`:
- Around line 146-177: Update the delete-dialog state around openDeleteDialog
and handleDeactivate to track statistics loading/error status and the request’s
target language identity. Ignore stale stats responses from previously selected
languages, and only enable the deactivation action when stats loaded
successfully with deleteStats.language_id matching deleteTarget.id; keep it
disabled while loading, after failure, or without a valid target.
- Around line 201-260: Update the languages table wrapper around the table in
LanguagesPage so horizontal overflow uses overflow-x-auto instead of
overflow-hidden, preserving the existing styling while allowing narrow viewports
to scroll to the status and action columns.
In `@src/components/pages/map/FieldMapPanel.tsx`:
- Around line 35-36: The map panel rendered by FieldMapPanel currently has no
mobile collapse behavior and obscures map content on narrow screens. Add a
responsive mobile layout with a visible close/collapse control, restoring the
previous collapsed behavior or implementing a bottom-sheet presentation, while
preserving the existing desktop panel layout.
In `@src/components/pages/projectAccess/OrgAccessSection.tsx`:
- Around line 12-70: Remove the organization access workflow: delete the
OrgAccessSection component and its grant/revoke callers, remove GrantOrgDialog
and all references to it, and delete the organization-inheritance copy from the
UserAccessSection empty state. Apply these changes in
src/components/pages/projectAccess/OrgAccessSection.tsx (12-70),
src/components/pages/projectAccess/GrantOrgDialog.tsx (21-90), and
src/components/pages/projectAccess/UserAccessSection.tsx (71-76).
In `@src/components/pages/projects/LocationSection.tsx`:
- Line 80: Update the manual-coordinate save flow around onSave so it also
synchronizes the local location state used by the marker and displayed
coordinates with the newly saved latitude, longitude, and name. Apply the same
update to the related coordinate-application paths, while preserving the
existing persistence behavior.
- Around line 67-80: Update handleSave to validate coordinates as a complete
pair: reject cases where only one of manualLat or manualLng is provided, require
the entire trimmed values to be valid numeric input rather than accepting
parseFloat prefixes, and enforce latitude between -90 and 90 and longitude
between -180 and 180 before calling onSave.
In `@src/components/pages/projects/ProjectFormDialog.tsx`:
- Around line 93-109: Update the platform-admin submission flow in
ProjectFormDialog so creating a new language and its project uses a single
transactional API operation rather than separate languagesAPI.create and
projectsAPI.create calls. Preserve the existing trimmed language/project fields
and language state refresh behavior, but route the combined creation through the
available transactional project-creation API and ensure either both records are
created or neither is persisted.
In `@src/components/pages/userDetail/index.tsx`:
- Around line 195-203: Update handleRoleSelect so selecting the platform_admin
role opens an explicit confirmation dialog instead of immediately calling
applyRoleUpdate. Add or reuse confirmation state and ensure the update is
submitted only after the user confirms, while preserving the existing manager
and member confirmation flows.
- Around line 71-99: Update the userId effect and the fetchUser/fetchRoles flows
to reset user and roles state and their loading flags immediately when the
identity changes, then abort or ignore any response associated with an obsolete
userId. Ensure stale requests cannot call setUser, setRoles, or leave loading
state for the newly routed user, while preserving the existing fetch error
handling and current-user behavior.
In `@src/components/pages/UsersPage/UserCard.tsx`:
- Around line 18-22: Update the clickable card in UserCard to use a
keyboard-accessible navigation element, preferably a Link targeting
`/app/users/${user.id}`, instead of the onClick-only div. Preserve the existing
styling and card content while removing the non-semantic click navigation.
In `@src/components/ui/dialog.tsx`:
- Line 41: Restore visible keyboard focus indicators on both
DialogPrimitive.Close in src/components/ui/dialog.tsx:41-41 and SelectTrigger in
src/components/ui/select.tsx:17-17 by adding equivalent focus-visible ring
styling, such as a 2px accent ring, alongside the existing focus outline
removal.
In `@src/index.css`:
- Around line 49-60: Update the CSS declarations in the affected sections to
satisfy Stylelint: lowercase the flagged font-family keywords, insert the
required empty line before margin declarations, and rename the keyframes to
kebab-case while updating every corresponding animation reference. Preserve the
existing styling and behavior.
In `@src/stores/phasesStore.ts`:
- Around line 51-52: Update reset() in phasesStore to invalidate any in-flight
fetches by aborting their requests or advancing a generation token. In fetch(),
verify the request generation is still current before applying phases,
dependencies, loading state, or lastFetched via set, discarding responses from
older generations so logout cannot repopulate the cache.
---
Outside diff comments:
In `@src/components/pages/phases/PhaseFlowGraph.tsx`:
- Around line 334-375: Make the phase node group rendered in PhaseFlowGraph.tsx
keyboard-accessible by adding focusability, button semantics, an accessible
name, and Enter/Space key handling that invokes handleNodeClick like the
existing click handler. Also update the clickable phase card in
ProjectPhasesTab.tsx to use a native button or equivalent keyboard interaction,
preserving its current activation behavior.
---
Other comments:
In `@src/components/common/FilterBar.tsx`:
- Around line 42-47: Update the search input in FilterBar’s input rendering to
include an explicit aria-label describing its search purpose, while preserving
the existing placeholder, value, and onChange behavior.
In `@src/components/common/LocationSearchInput.tsx`:
- Around line 128-134: Add aria-label="Clear location" to the icon-only button
rendering the X icon in LocationSearchInput, while preserving its existing
handleClear behavior and styling.
In `@src/components/common/UserAvatar.tsx`:
- Around line 21-24: Update UserAvatar so its imgError state resets whenever
avatarUrl changes, allowing a newly provided URL to render after a previous
image failure. Add the reset alongside the existing state logic while preserving
the current showImage condition for unchanged URLs.
In `@src/components/layout/Sidebar.tsx`:
- Around line 182-186: Update the useEffect that invokes fetchCounts so it runs
for both platform admins and managers, using the existing manager-access
condition alongside isPlatformAdmin. Preserve the current dependency tracking
and avoid restricting count fetching to platform-admin sessions.
In `@src/components/pages/dashboard/AdminStatsRow.tsx`:
- Around line 19-34: Update the pending reviews subtitle in AdminStatsRow so
pendingPublic requests are reported as a distinct category instead of being
included under the change count. Keep changeCount’s existing total contribution
and add the corresponding public-request count to the displayed breakdown.
In `@src/components/pages/dashboard/NeedsReviewPanel.tsx`:
- Around line 78-82: Update the “All requests” Link in the NeedsReviewPanel
heading so it does not route to the access-only /app/users destination; either
remove the link, relabel it “Access requests” to match that route, or point it
to an existing combined review page covering access, change, and public
requests.
In `@src/components/pages/PhasesPage.tsx`:
- Around line 213-220: Make the phase rows rendered in the PhasesPage table
keyboard-accessible by adding focusability and keyboard activation alongside the
existing onClick handler. Update the row identified by key={phase.id} to respond
to Enter and Space by selecting the phase, while preserving mouse selection and
the existing visual styling.
In `@src/components/pages/projectAccess/GrantUserDialog.tsx`:
- Around line 61-76: Provide programmatic names for all newly introduced
controls: in src/components/pages/projectAccess/GrantUserDialog.tsx lines 61-76,
connect the role Label and SelectTrigger with matching htmlFor and id values; in
src/components/pages/userDetail/UserHeader.tsx lines 20-42, add an explicit
upload-photo aria-label; in src/components/pages/apps/AutoApproveCard.tsx lines
20-24, associate the control with its label or add an auto-approve aria-label;
and in src/components/pages/ProjectDetailPage.tsx lines 191-200, add an explicit
upload-project-image aria-label.
In `@src/components/pages/ProjectsPage.tsx`:
- Around line 92-100: Update the EmptyState action label in the projectsView
empty-state branch to use the role-aware label that matches openCreateDialog’s
workflow, showing the request-oriented text for managers while preserving the
create-project label for other roles.
In `@src/components/pages/publicRequest/ProjectRequestForm.tsx`:
- Around line 90-100: Update the 409 handling in ProjectRequestForm to display
the server-provided detail when available, with a neutral fallback message that
covers any conflict rather than assuming a language duplicate. Leave the
existing 429, 422, and 400 handling unchanged.
In `@src/components/pages/userDetail/AccountCard.tsx`:
- Around line 29-38: The Switch in AccountCard needs an accessible name because
the adjacent status text is not associated with it. Update the Switch usage in
the account-status section to provide an accessible label, reusing the existing
Active/Inactive status context without changing the onToggleActive behavior.
In `@src/components/pages/userDetail/GlobalRoleCard.tsx`:
- Around line 25-56: Expose selection state in the roleChoices button controls
by adding aria-pressed={selected} to each button, or alternatively implement a
radiogroup with aria-checked. Preserve the existing selected styling and
roleSaving behavior while ensuring assistive technology can identify the current
role.
In `@src/components/ui/button.tsx`:
- Around line 11-17: The shared destructive variant in
src/components/ui/button.tsx lines 11-17 must use distinct danger styling
instead of duplicating the default variant. Update the delete-app button usage
in src/components/pages/apps/DangerZoneCard.tsx lines 10-17 to set
variant="destructive", while preserving the existing button behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Pro Plus
Run ID: 3ce9a9d8-9b3f-4139-baf6-032ec29abbd0
⛔ Files ignored due to path filters (4)
public/assets/icon-branco.svgis excluded by!**/*.svgpublic/assets/icon-telha.svgis excluded by!**/*.svgpublic/assets/logo-branco.svgis excluded by!**/*.svgpublic/assets/logo-verde.svgis excluded by!**/*.svg
📒 Files selected for processing (119)
.env.example.gitignoreCLAUDE.mdeslint.config.jsindex.htmlsrc/App.tsxsrc/components/common/EmptyState.tsxsrc/components/common/ErrorBoundary.tsxsrc/components/common/FeatureSpotlight.tsxsrc/components/common/FilterBar.tsxsrc/components/common/ImageUpload.tsxsrc/components/common/InfoTooltip.tsxsrc/components/common/LoadingSpinner.tsxsrc/components/common/LocationSearchInput.tsxsrc/components/common/PlatformMultiSelect.tsxsrc/components/common/ProfileDialog.tsxsrc/components/common/ReCaptcha.tsxsrc/components/common/StatCard.tsxsrc/components/common/UserAvatar.tsxsrc/components/common/UserSearchPicker.tsxsrc/components/layout/AppHeader.tsxsrc/components/layout/AppShell.tsxsrc/components/layout/Sidebar.tsxsrc/components/pages/AccessDeniedPage.tsxsrc/components/pages/AccessRequestsSection.tsxsrc/components/pages/AppDetailPage.tsxsrc/components/pages/AppsPage.tsxsrc/components/pages/ChangeRequestsSection.tsxsrc/components/pages/DashboardPage.tsxsrc/components/pages/LanguagesPage.tsxsrc/components/pages/LoginPage.tsxsrc/components/pages/MapPage.tsxsrc/components/pages/NotFoundPage.tsxsrc/components/pages/OrganizationDetailPage.tsxsrc/components/pages/OrganizationsPage.tsxsrc/components/pages/PhasesPage.tsxsrc/components/pages/ProjectAccessTab.tsxsrc/components/pages/ProjectDetailPage.tsxsrc/components/pages/ProjectPhasesTab.tsxsrc/components/pages/ProjectsPage.tsxsrc/components/pages/ReviewDialog.tsxsrc/components/pages/UserDetailPage.tsxsrc/components/pages/UsersPage/UserCard.tsxsrc/components/pages/UsersPage/index.tsxsrc/components/pages/apps/AppCard.tsxsrc/components/pages/apps/AppFormDialog.tsxsrc/components/pages/apps/AutoApproveCard.tsxsrc/components/pages/apps/DangerZoneCard.tsxsrc/components/pages/apps/DetailsCard.tsxsrc/components/pages/apps/RolesCard.tsxsrc/components/pages/changeRequests/ChangeRequestCard.tsxsrc/components/pages/changeRequests/MyChangeRequestsSection.tsxsrc/components/pages/changeRequests/reviewableRequest.tssrc/components/pages/dashboard/AdminDashboard.tsxsrc/components/pages/dashboard/AdminStatsRow.tsxsrc/components/pages/dashboard/AppCard.tsxsrc/components/pages/dashboard/MapPreview.tsxsrc/components/pages/dashboard/MyAppsCard.tsxsrc/components/pages/dashboard/NeedsReviewPanel.tsxsrc/components/pages/dashboard/useAdminDashboardData.tssrc/components/pages/map/FieldMapPanel.tsxsrc/components/pages/map/ProjectPopupContent.tsxsrc/components/pages/phases/DependencyPanel.tsxsrc/components/pages/phases/PhaseFlowGraph.tsxsrc/components/pages/projectAccess/GrantOrgDialog.tsxsrc/components/pages/projectAccess/GrantUserDialog.tsxsrc/components/pages/projectAccess/OrgAccessSection.tsxsrc/components/pages/projectAccess/RevokeButton.tsxsrc/components/pages/projectAccess/UserAccessSection.tsxsrc/components/pages/projectAccess/initials.tssrc/components/pages/projects/LocationSection.tsxsrc/components/pages/projects/ProjectCard.tsxsrc/components/pages/projects/ProjectFormDialog.tsxsrc/components/pages/projects/ProjectInfoForm.tsxsrc/components/pages/publicRequest/LanguageCombobox.tsxsrc/components/pages/publicRequest/LanguageRequestForm.tsxsrc/components/pages/publicRequest/ProjectRequestForm.tsxsrc/components/pages/publicRequest/index.tsxsrc/components/pages/userDetail/AccountCard.tsxsrc/components/pages/userDetail/AppRolesCard.tsxsrc/components/pages/userDetail/GlobalRoleCard.tsxsrc/components/pages/userDetail/UserHeader.tsxsrc/components/pages/userDetail/index.tsxsrc/components/pages/userDetail/roles.tssrc/components/ui/badge.tsxsrc/components/ui/button.tsxsrc/components/ui/card.tsxsrc/components/ui/dialog.tsxsrc/components/ui/input.tsxsrc/components/ui/label.tsxsrc/components/ui/popover.tsxsrc/components/ui/select.tsxsrc/components/ui/switch.tsxsrc/components/ui/tabs.tsxsrc/components/ui/textarea.tsxsrc/components/ui/tooltip.tsxsrc/constants/platforms.tssrc/contexts/AuthContext.tsxsrc/index.csssrc/services/api.tssrc/stores/languagesStore.tssrc/stores/phasesStore.tssrc/stores/requestCountsStore.tssrc/styles/badges.tssrc/styles/cards.tssrc/styles/layout.tssrc/styles/states.tssrc/types/app.tssrc/types/auth.tssrc/types/changeRequest.tssrc/types/index.tssrc/types/language.tssrc/types/phase.tssrc/types/project.tssrc/types/publicRequest.tssrc/types/user.tssrc/utils/avatar.tssrc/utils/format.tssrc/vite-env.d.ts
💤 Files with no reviewable changes (4)
- src/components/pages/dashboard/AdminDashboard.tsx
- src/components/pages/UserDetailPage.tsx
- src/components/pages/OrganizationDetailPage.tsx
- src/components/pages/OrganizationsPage.tsx
| <Route path="apps" element={<AdminRoute><AppsPage /></AdminRoute>} /> | ||
| <Route path="apps/:appId" element={<AdminRoute><AppDetailPage /></AdminRoute>} /> | ||
| <Route path="phases" element={<PhasesPage />} /> | ||
| <Route path="phases" element={<AdminRoute><PhasesPage /></AdminRoute>} /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Align phase routing with the manager-editable contract.
AdminRoute rejects every non-platform-admin, while PhasesPage says managers may edit the global phase catalog. Use an authorization wrapper accepting isPlatformAdmin || isManager, or update the documented requirement and page copy if phases are intentionally admin-only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/App.tsx` at line 55, Update the phases route in App.tsx to use an
authorization wrapper that permits users when isPlatformAdmin or isManager is
true, preserving platform-admin access while allowing managers to edit the
global phase catalog; only change the page documentation/copy instead if the
intended contract is admin-only.
| {mobileOpen && ( | ||
| <div className="fixed inset-0 z-40 lg:hidden"> | ||
| <div className="absolute inset-0 bg-preto/50 animate-fade-in" onClick={onMobileClose} /> | ||
| <aside className="relative z-50 h-full w-[min(258px,calc(100vw-3rem))] bg-shell text-shell-fg flex flex-col px-3.5 pt-[22px] pb-4"> | ||
| <button | ||
| onClick={onMobileClose} | ||
| aria-label="Close menu" | ||
| className="absolute right-3 top-3 w-8 h-8 rounded-[9px] grid place-items-center text-[var(--shell-dim)] hover:bg-[var(--shell-active)] hover:text-shell-fg transition-colors" | ||
| > | ||
| <X className="w-4 h-4" strokeWidth={2} /> | ||
| </button> | ||
| <SidebarContent | ||
| sections={sections} | ||
| onNavigate={onMobileClose} | ||
| onProfile={openProfile} | ||
| onLogout={logout} | ||
| userId={user?.id} | ||
| userName={userName} | ||
| userEmail={user?.email ?? ""} | ||
| userRole={userRole} | ||
| avatarUrl={user?.avatar_url} | ||
| /> | ||
| </aside> | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Give the mobile drawer proper modal keyboard behavior.
The overlay does not trap focus, close on Escape, restore focus, or expose dialog semantics. Keyboard users can tab into obscured page content. Use the existing dialog/sheet primitive or implement equivalent focus management.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/layout/Sidebar.tsx` around lines 240 - 263, Update the mobile
drawer rendered by the mobileOpen branch in SidebarContent usage to provide
modal dialog behavior: trap focus within the open drawer, close it on Escape,
restore focus to the triggering element after closing, and expose appropriate
dialog semantics. Prefer the existing dialog/sheet primitive if available;
otherwise implement equivalent focus management while preserving the current
onMobileClose behavior.
| <div | ||
| onClick={onOpen} | ||
| className={cn( | ||
| "group relative bg-elevated rounded-[16px] shadow-[var(--shadow-card)] p-[18px] flex flex-col gap-3 cursor-pointer transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[var(--shadow-md)]", | ||
| !app.is_active && "opacity-70", | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Replace mouse-only card interactions with semantic, focus-visible controls. Both cards use non-focusable click handlers for their primary action and hide secondary actions until hover.
src/components/pages/apps/AppCard.tsx#L26-L31: use a link or button for opening the app.src/components/pages/apps/AppCard.tsx#L77-L92: reveal edit/delete actions on keyboard focus and non-hover devices.src/components/pages/projects/ProjectCard.tsx#L33-L44: make project navigation keyboard operable and reveal editing on focus.
📍 Affects 2 files
src/components/pages/apps/AppCard.tsx#L26-L31(this comment)src/components/pages/apps/AppCard.tsx#L77-L92src/components/pages/projects/ProjectCard.tsx#L33-L44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/pages/apps/AppCard.tsx` around lines 26 - 31, Replace the
non-focusable onClick container in AppCard with a semantic link or button for
opening the app, preserving its existing styling and action. In
src/components/pages/apps/AppCard.tsx lines 77-92, make edit/delete controls
visible on keyboard focus and devices without hover. In
src/components/pages/projects/ProjectCard.tsx lines 33-44, make project
navigation keyboard-operable and reveal the editing action on focus.
| <div className="space-y-2"> | ||
| <Label> | ||
| Platforms <span className="font-normal text-fg-subtle">(minimum 1)</span> | ||
| </Label> | ||
| <PlatformMultiSelect | ||
| value={form.platforms} | ||
| onChange={(platforms) => setForm((f) => ({ ...f, platforms }))} | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Enforce the stated minimum platform count.
The save action remains enabled when form.platforms is empty, despite the “minimum 1” requirement.
Proposed fix
-<Button onClick={onSave} disabled={saving || !form.name.trim()}>
+<Button
+ onClick={onSave}
+ disabled={saving || !form.name.trim() || form.platforms.length === 0}
+>Also applies to: 106-109
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/pages/apps/DetailsCard.tsx` around lines 65 - 72, Update the
save-action validation in the form containing the Platforms
`PlatformMultiSelect` so it is disabled whenever `form.platforms` is empty,
enforcing the displayed minimum of one platform. Preserve the existing save
behavior when at least one platform is selected, including the corresponding
validation path noted near the alternate save control.
| <input | ||
| id="f_rl_name" | ||
| placeholder="e.g. Reviewer" | ||
| value={roleForm.label} | ||
| onChange={(e) => setRoleForm((f) => ({ ...f, label: e.target.value }))} | ||
| className="w-full bg-elevated rounded-[10px] px-3 py-2 text-[13px] text-fg-strong placeholder:text-fg-subtle shadow-[var(--shadow-sm)] focus:outline-none" | ||
| /> | ||
| </div> | ||
| <div className="flex-1 sm:max-w-[220px]"> | ||
| <label | ||
| htmlFor="f_rl_key" | ||
| className="block text-[11px] font-semibold tracking-[0.08em] uppercase text-fg-subtle mb-1" | ||
| > | ||
| role_key | ||
| </label> | ||
| <input | ||
| id="f_rl_key" | ||
| placeholder="e.g. reviewer" | ||
| value={roleForm.role_key} | ||
| onChange={(e) => setRoleForm((f) => ({ ...f, role_key: e.target.value }))} | ||
| className="w-full bg-elevated rounded-[10px] px-3 py-2 text-[13px] font-mono text-fg-strong placeholder:text-fg-subtle shadow-[var(--shadow-sm)] focus:outline-none" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Restore a visible keyboard focus indicator.
Both inputs remove their outline without providing a replacement. Prefer the shared Input, or add an explicit focus-visible ring.
Example fix
-className="w-full ... focus:outline-none"
+className="w-full ... focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <input | |
| id="f_rl_name" | |
| placeholder="e.g. Reviewer" | |
| value={roleForm.label} | |
| onChange={(e) => setRoleForm((f) => ({ ...f, label: e.target.value }))} | |
| className="w-full bg-elevated rounded-[10px] px-3 py-2 text-[13px] text-fg-strong placeholder:text-fg-subtle shadow-[var(--shadow-sm)] focus:outline-none" | |
| /> | |
| </div> | |
| <div className="flex-1 sm:max-w-[220px]"> | |
| <label | |
| htmlFor="f_rl_key" | |
| className="block text-[11px] font-semibold tracking-[0.08em] uppercase text-fg-subtle mb-1" | |
| > | |
| role_key | |
| </label> | |
| <input | |
| id="f_rl_key" | |
| placeholder="e.g. reviewer" | |
| value={roleForm.role_key} | |
| onChange={(e) => setRoleForm((f) => ({ ...f, role_key: e.target.value }))} | |
| className="w-full bg-elevated rounded-[10px] px-3 py-2 text-[13px] font-mono text-fg-strong placeholder:text-fg-subtle shadow-[var(--shadow-sm)] focus:outline-none" | |
| <input | |
| id="f_rl_name" | |
| placeholder="e.g. Reviewer" | |
| value={roleForm.label} | |
| onChange={(e) => setRoleForm((f) => ({ ...f, label: e.target.value }))} | |
| className="w-full bg-elevated rounded-[10px] px-3 py-2 text-[13px] text-fg-strong placeholder:text-fg-subtle shadow-[var(--shadow-sm)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent" | |
| /> | |
| </div> | |
| <div className="flex-1 sm:max-w-[220px]"> | |
| <label | |
| htmlFor="f_rl_key" | |
| className="block text-[11px] font-semibold tracking-[0.08em] uppercase text-fg-subtle mb-1" | |
| > | |
| role_key | |
| </label> | |
| <input | |
| id="f_rl_key" | |
| placeholder="e.g. reviewer" | |
| value={roleForm.role_key} | |
| onChange={(e) => setRoleForm((f) => ({ ...f, role_key: e.target.value }))} | |
| className="w-full bg-elevated rounded-[10px] px-3 py-2 text-[13px] font-mono text-fg-strong placeholder:text-fg-subtle shadow-[var(--shadow-sm)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/pages/apps/RolesCard.tsx` around lines 70 - 90, Update the
role label and role_key inputs in RolesCard to provide a visible keyboard focus
indicator instead of only removing the outline. Prefer replacing them with the
shared Input component; otherwise add the established focus-visible ring styling
to both inputs while preserving their existing behavior and appearance.
| function handleRoleSelect(role: UserRole) { | ||
| if (!user || role === getUserRole(user)) return | ||
| if (role === "platform_admin") { | ||
| applyRoleUpdate({ role: "platform_admin" }) | ||
| } else if (role === "manager") { | ||
| openManagerDialog() | ||
| } else { | ||
| setMemberConfirmOpen(true) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Require confirmation before granting platform-admin privileges.
Selecting platform_admin immediately submits the elevation, while lower-impact role changes require dialogs. An accidental click therefore grants unrestricted platform access. Add an explicit confirmation step.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/pages/userDetail/index.tsx` around lines 195 - 203, Update
handleRoleSelect so selecting the platform_admin role opens an explicit
confirmation dialog instead of immediately calling applyRoleUpdate. Add or reuse
confirmation state and ensure the update is submitted only after the user
confirms, while preserving the existing manager and member confirmation flows.
| return ( | ||
| <div | ||
| className={cn(card.interactive, "p-5 group")} | ||
| className={cn(card.interactive, "p-4 flex items-center gap-3")} | ||
| onClick={() => navigate(`/app/users/${user.id}`)} | ||
| > |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use a keyboard-accessible navigation element.
The clickable div cannot receive focus or activate via keyboard. Render the card as a Link or semantic button instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/pages/UsersPage/UserCard.tsx` around lines 18 - 22, Update the
clickable card in UserCard to use a keyboard-accessible navigation element,
preferably a Link targeting `/app/users/${user.id}`, instead of the onClick-only
div. Preserve the existing styling and card content while removing the
non-semantic click navigation.
| > | ||
| {children} | ||
| <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-telha focus:ring-offset-2 disabled:pointer-events-none"> | ||
| <DialogPrimitive.Close className="absolute right-4 top-4 w-8 h-8 rounded-[9px] grid place-items-center text-fg-subtle hover:bg-muted hover:text-fg-strong transition-colors focus:outline-none disabled:pointer-events-none"> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Missing keyboard focus indicators after removing focus:outline-none. Both the dialog close button and the SelectTrigger remove the default focus outline without providing an alternative indicator (focus:ring-*, focus:border-*, etc.). Keyboard users get no visual feedback when tabbing to these controls.
src/components/ui/dialog.tsx#L41-L41: add afocus-visible:ring-2 focus-visible:ring-accent/40(or equivalent) to theDialogPrimitive.CloseclassName.src/components/ui/select.tsx#L17-L17: add afocus-visible:ring-2 focus-visible:ring-accent/40(or equivalent) to theSelectTriggerclassName.
📍 Affects 2 files
src/components/ui/dialog.tsx#L41-L41(this comment)src/components/ui/select.tsx#L17-L17
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/ui/dialog.tsx` at line 41, Restore visible keyboard focus
indicators on both DialogPrimitive.Close in src/components/ui/dialog.tsx:41-41
and SelectTrigger in src/components/ui/select.tsx:17-17 by adding equivalent
focus-visible ring styling, such as a 2px accent ring, alongside the existing
focus outline removal.
| --font-sans: "Montserrat", ui-sans-serif, system-ui, sans-serif; | ||
| --font-serif: "Merriweather", ui-serif, Georgia, serif; | ||
| --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; | ||
| } | ||
|
|
||
| :root { | ||
| --ease-out: cubic-bezier(0.2, 0.8, 0.25, 1); | ||
| --shell-dim: rgba(246, 245, 235, 0.62); | ||
| --shell-line: rgba(246, 245, 235, 0.13); | ||
| --shell-active: rgba(246, 245, 235, 0.12); | ||
| --edge: #6B6A52; | ||
| --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Fix the new Stylelint violations.
Lowercase the flagged font-family keywords, add an empty line before margin, and rename the keyframes to kebab-case with their references updated. The current declarations fail Stylelint.
Representative fix
- --font-serif: "Merriweather", ui-serif, Georgia, serif;
- --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ --font-serif: "Merriweather", ui-serif, georgia, serif;
+ --font-mono: ui-monospace, sfmono-regular, menlo, consolas, monospace;
...
- --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ --mono: ui-monospace, sfmono-regular, menlo, consolas, monospace;
...
body {
`@apply` bg-canvas text-fg font-sans antialiased;
+
margin: 0;
}
...
-@keyframes tIn {
+@keyframes t-in {
...
-@keyframes fadeIn {
+@keyframes fade-in {
...
-@keyframes popIn {
+@keyframes pop-in {
...
-@keyframes pulseRing {
+@keyframes pulse-ring {
...
-.animate-fade-in { animation: fadeIn 0.16s ease-out }
+.animate-fade-in { animation: fade-in 0.16s ease-out }Also applies to: 119-136
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 50-50: Expected "Georgia" to be "georgia" (value-keyword-case)
(value-keyword-case)
[error] 51-51: Expected "SFMono-Regular" to be "sfmono-regular" (value-keyword-case)
(value-keyword-case)
[error] 51-51: Expected "Menlo" to be "menlo" (value-keyword-case)
(value-keyword-case)
[error] 51-51: Expected "Consolas" to be "consolas" (value-keyword-case)
(value-keyword-case)
[error] 60-60: Expected "SFMono-Regular" to be "sfmono-regular" (value-keyword-case)
(value-keyword-case)
[error] 60-60: Expected "Menlo" to be "menlo" (value-keyword-case)
(value-keyword-case)
[error] 60-60: Expected "Consolas" to be "consolas" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/index.css` around lines 49 - 60, Update the CSS declarations in the
affected sections to satisfy Stylelint: lowercase the flagged font-family
keywords, insert the required empty line before margin declarations, and rename
the keyframes to kebab-case while updating every corresponding animation
reference. Preserve the existing styling and behavior.
Source: Linters/SAST tools
| reset: () => { | ||
| set({ phases: [], dependencies: new Map(), loading: false, lastFetched: null }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Prevent in-flight fetches from undoing logout resets.
reset() clears the cache, but an already-running fetch() can subsequently repopulate it with the previous user’s phases and a fresh TTL. Abort outstanding requests or increment a generation token in reset(), then discard responses from older generations before calling set.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/stores/phasesStore.ts` around lines 51 - 52, Update reset() in
phasesStore to invalidate any in-flight fetches by aborting their requests or
advancing a generation token. In fetch(), verify the request generation is still
current before applying phases, dependencies, loading state, or lastFetched via
set, discarding responses from older generations so logout cannot repopulate the
cache.
Platform admins land on the admin dashboard, so the Main nav item now reads "Dashboard" for them; managers keep "My Apps". Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Platform admins get a restore action on inactive languages, calling
POST /languages/{id}/reactivate, and inactive rows now render on a
darker fill so Active vs Inactive reads clearly, per the design.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Review follow-ups (platform-admin fixes)Pushed three fixes on top of the redesign:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/pages/LanguagesPage.tsx (1)
69-73: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
refreshLanguagesdoesn't wait for the inactive-list reload, so the new reactivate/deactivate flows can show stale rows.
loadAllLanguages()is fired withoutawait/return, soawait refreshLanguages()inhandleReactivate/handleDeactivate/handleSaveonly waits onfetchLanguages(). WhenshowInactiveis true,allLanguages(what actually renders the row) can still hold the pre-reactivation snapshot after the success toast fires andreactivatingIdis cleared, letting an admin re-click "Reactivate" on a row that already succeeded before the UI catches up.Proposed fix
- function refreshLanguages() { + async function refreshLanguages() { useLanguagesStore.getState().invalidate() - if (showInactive) loadAllLanguages() - return fetchLanguages() + const [result] = await Promise.all([ + fetchLanguages(), + showInactive ? loadAllLanguages() : Promise.resolve(), + ]) + return result }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/LanguagesPage.tsx` around lines 69 - 73, Update refreshLanguages so it waits for loadAllLanguages to complete when showInactive is true, while preserving the existing fetchLanguages refresh and return behavior. Ensure callers awaiting refreshLanguages do not continue until both language lists are refreshed.
♻️ Duplicate comments (2)
src/components/pages/LanguagesPage.tsx (2)
221-221: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTable wrapper clips actions on narrow viewports.
overflow-hiddenon the table container clips the Status/Actions columns instead of allowing horizontal scroll, so edit/deactivate/reactivate controls become unreachable on narrow screens.Proposed fix
- <div className="bg-elevated rounded-[18px] shadow-[var(--shadow-card)] overflow-hidden"> + <div className="bg-elevated rounded-[18px] shadow-[var(--shadow-card)] overflow-x-auto">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/LanguagesPage.tsx` at line 221, Update the table wrapper div in LanguagesPage to remove overflow-hidden and allow horizontal scrolling on narrow viewports, preserving the existing rounded corners, elevated background, and card shadow while keeping the Status and Actions controls reachable.
147-178: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDeactivate action stays enabled while stats are loading/failed, and can show mismatched project data.
openDeleteDialogfetches stats asynchronously but the dialog's Deactivate button (line 470) is only gated ondeleting, not on whetherdeleteStatshas loaded for the currentdeleteTarget. Selecting a different language before the previous stats request resolves can also let a stale response overwritedeleteStatsfor the wrong target, showing an incorrect project-impact message before confirmation.Track a loading/error state and verify
deleteStats?.language_id === deleteTarget?.idbefore enabling the destructive action.Also applies to: 465-472
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/LanguagesPage.tsx` around lines 147 - 178, Update openDeleteDialog and the dialog’s Deactivate action to track stats loading/error state, disable deactivation until stats successfully load for the current deleteTarget, and require deleteStats?.language_id to match deleteTarget?.id. Prevent stale asynchronous stats responses from updating deleteStats after the selected target changes, preserving the existing handleDeactivate behavior once validated stats are available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/components/pages/LanguagesPage.tsx`:
- Around line 69-73: Update refreshLanguages so it waits for loadAllLanguages to
complete when showInactive is true, while preserving the existing fetchLanguages
refresh and return behavior. Ensure callers awaiting refreshLanguages do not
continue until both language lists are refreshed.
---
Duplicate comments:
In `@src/components/pages/LanguagesPage.tsx`:
- Line 221: Update the table wrapper div in LanguagesPage to remove
overflow-hidden and allow horizontal scrolling on narrow viewports, preserving
the existing rounded corners, elevated background, and card shadow while keeping
the Status and Actions controls reachable.
- Around line 147-178: Update openDeleteDialog and the dialog’s Deactivate
action to track stats loading/error state, disable deactivation until stats
successfully load for the current deleteTarget, and require
deleteStats?.language_id to match deleteTarget?.id. Prevent stale asynchronous
stats responses from updating deleteStats after the selected target changes,
preserving the existing handleDeactivate behavior once validated stats are
available.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Pro Plus
Run ID: 412b6b88-d206-4580-9be8-9490854f765c
📒 Files selected for processing (3)
src/components/layout/Sidebar.tsxsrc/components/pages/LanguagesPage.tsxsrc/services/api.ts
… and icon The project and app detail headers only exposed a bare, unlabeled tile plus a lone Remove link, so there was no obvious way to add or change the image. Both headers now show an accent Upload image/Upload icon (or Change) link alongside Remove. The app icon editor moves to the header, dropping the duplicate control from the app DetailsCard. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Rebuild the marker popup with Tailwind + semantic tokens: title, a single location line, description, phase pills with the current phase highlighted in telha, a compact meta line, and an accent Open project button. Drops the inline hex styles and hand-drawn SVGs. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add a collapse toggle that shrinks the rail to a 72px icon-only mode (icons + corner-badge counts + title tooltips, hairline section dividers, single-icon theme toggle). State persists via a new sidebarStore (Zustand + localStorage tc_sidebar). Mobile keeps the full overlay drawer. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The logo, Public requests pill and Sign in link scrolled away with the page. Make the header sticky top-0 with a canvas background. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Give ImageUpload an xl size plus upload/change/hint labels and use them in the profile dialog for a cleaner, larger-photo layout. Add isolate to the app <main> so body-portaled dialogs paint above the Leaflet map (which sat at a higher z-index in the shared root stacking context). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
UI fixes batch (platform-admin review round)
|
There was a problem hiding this comment.
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
CLAUDE.md (1)
464-489: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAlign the sidebar example with role-specific labels.
Line 471 still documents “My Apps” as the default for all console users, but platform admins now see “Dashboard” while managers see “My Apps.” Update the sidebar mockup and role notes to match the implemented RBAC behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLAUDE.md` around lines 464 - 489, Update the Sidebar documentation mockup so the Main item is labeled “Dashboard” for platform admins and “My Apps” for managers, replacing the claim that “My Apps” is the default for all console users. Revise the related role notes to explicitly describe this role-specific landing behavior while preserving the existing Content and Administration visibility rules.src/components/pages/ProjectDetailPage.tsx (1)
1-11: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winImport
ChangeEventbefore using it in the handler signature.
React.ChangeEventhas no localReactbinding in this file; importtype ChangeEventfromreactand update the parameter type.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/ProjectDetailPage.tsx` around lines 1 - 11, Update the imports in ProjectDetailPage to include the type-only ChangeEvent symbol from react, then change the handler’s parameter annotation from React.ChangeEvent to ChangeEvent so it uses the available local type binding.
🟡 Other comments (1)
src/components/pages/map/ProjectPopupContent.tsx-59-81 (1)
59-81: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExpose each phase status as text, not only a colored dot.
Non-active chips render status exclusively via
PHASE_DOT; add an accessible status label and hide the decorative dot from assistive technology.Proposed fix
<span + aria-hidden="true" className={`h-1.5 w-1.5 flex-none rounded-full ${ active ? "bg-white/85" : PHASE_DOT[phase.status] }`} /> {phase.phase_name} + <span className="sr-only"> — {phase.status.replace("_", " ")}</span>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/map/ProjectPopupContent.tsx` around lines 59 - 81, Update the phase chip rendering inside ProjectPopupContent’s phases.map callback to expose phase.status as visible text for non-active phases, while preserving the existing PHASE_DOT color indicator. Mark the colored dot as decorative for assistive technology, and ensure the status text is accessible without changing the active-chip styling.
🧹 Nitpick comments (1)
src/components/pages/AppDetailPage.tsx (1)
52-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared image upload flow
Both pages duplicate the same MIME/size checks anduploadsAPI.imagecall. Move that file-selection/upload logic intoImageUploador a small shared hook, then let each page keep only its page-specific persistence (form.icon_urlvsprojectsAPI.update(...)).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/AppDetailPage.tsx` around lines 52 - 81, Extract the duplicated image validation, upload, busy-state, error handling, and input-reset logic from handleIconFile in src/components/pages/AppDetailPage.tsx (lines 52-81) and the corresponding handler in src/components/pages/ProjectDetailPage.tsx (lines 104-129) into ImageUpload or a shared hook. Keep each page responsible only for its persistence behavior: updating form.icon_url in AppDetailPage and calling projectsAPI.update(...) in ProjectDetailPage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@CLAUDE.md`:
- Around line 464-489: Update the Sidebar documentation mockup so the Main item
is labeled “Dashboard” for platform admins and “My Apps” for managers, replacing
the claim that “My Apps” is the default for all console users. Revise the
related role notes to explicitly describe this role-specific landing behavior
while preserving the existing Content and Administration visibility rules.
In `@src/components/pages/ProjectDetailPage.tsx`:
- Around line 1-11: Update the imports in ProjectDetailPage to include the
type-only ChangeEvent symbol from react, then change the handler’s parameter
annotation from React.ChangeEvent to ChangeEvent so it uses the available local
type binding.
---
Other comments:
In `@src/components/pages/map/ProjectPopupContent.tsx`:
- Around line 59-81: Update the phase chip rendering inside
ProjectPopupContent’s phases.map callback to expose phase.status as visible text
for non-active phases, while preserving the existing PHASE_DOT color indicator.
Mark the colored dot as decorative for assistive technology, and ensure the
status text is accessible without changing the active-chip styling.
---
Nitpick comments:
In `@src/components/pages/AppDetailPage.tsx`:
- Around line 52-81: Extract the duplicated image validation, upload,
busy-state, error handling, and input-reset logic from handleIconFile in
src/components/pages/AppDetailPage.tsx (lines 52-81) and the corresponding
handler in src/components/pages/ProjectDetailPage.tsx (lines 104-129) into
ImageUpload or a shared hook. Keep each page responsible only for its
persistence behavior: updating form.icon_url in AppDetailPage and calling
projectsAPI.update(...) in ProjectDetailPage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Pro Plus
Run ID: df2f60c7-82b5-4580-a286-df943e11be98
📒 Files selected for processing (11)
CLAUDE.mdsrc/components/common/ImageUpload.tsxsrc/components/common/ProfileDialog.tsxsrc/components/layout/AppShell.tsxsrc/components/layout/Sidebar.tsxsrc/components/pages/AppDetailPage.tsxsrc/components/pages/ProjectDetailPage.tsxsrc/components/pages/apps/DetailsCard.tsxsrc/components/pages/map/ProjectPopupContent.tsxsrc/components/pages/publicRequest/index.tsxsrc/stores/sidebarStore.ts
💤 Files with no reviewable changes (1)
- src/components/pages/apps/DetailsCard.tsx
The popup only highlighted the in-progress phase, so completed, blocked and not-started phases all rendered as the same grey chip and the card looked unchanged when a status moved. Phase colors now come from a shared PHASE_STATUS_CONFIG, reused by the project phases graph, and the popup follows the design card: plain location line, tinted status chips (capped at four plus a +N chip), no divider above the meta line and a pill "Open project" button. The map also picks the dark CARTO tiles in dark mode and keeps the popup close button. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…in use The Projects column was a hardcoded dash, so there was no way to see how many projects use a language. It now counts the projects visible to the user, and the deactivate dialog matches the design: it is titled after the language, seeds the project list from that count so it renders without a flash, and when the language is in use it shows the "in use — deactivation blocked" callout and disables the confirm button. A 409 from the API is surfaced as its own toast. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/pages/MapPage.tsx (1)
68-69: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not discard map projects when language loading fails.
Promise.allrejects whenfetchLanguages()fails, sosetProjects(projectsRes.data)is skipped and this no-op catch renders an empty map despite a successful/projectsresponse. Load projects independently and treat missing language labels as optional.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/MapPage.tsx` around lines 68 - 69, Update the map-loading flow in MapPage so a fetchLanguages failure does not prevent successful project data from being passed to setProjects. Load the projects response independently, treat language labels as optional when enriching projects, and remove the no-op catch that discards the project list.
🟡 Other comments (1)
src/constants/phaseStatus.ts-35-40 (1)
35-40: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the blocked pill on the warning palette.
dotandringusest-warn, butpilluses the accent palette. Use the shared warning tokens consistently so blocked phases do not appear as the brand accent.Proposed fix
blocked: { label: "Blocked", - pill: "bg-accent-soft text-on-accent-soft", + pill: "bg-st-warn/15 text-st-warn", dot: "bg-st-warn", ring: "ring-st-warn/40", icon: AlertTriangle,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/constants/phaseStatus.ts` around lines 35 - 40, Update the blocked status entry in phaseStatus to use the shared st-warn warning palette for pill, matching its existing dot and ring tokens; leave the other status properties unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/components/pages/MapPage.tsx`:
- Around line 68-69: Update the map-loading flow in MapPage so a fetchLanguages
failure does not prevent successful project data from being passed to
setProjects. Load the projects response independently, treat language labels as
optional when enriching projects, and remove the no-op catch that discards the
project list.
---
Other comments:
In `@src/constants/phaseStatus.ts`:
- Around line 35-40: Update the blocked status entry in phaseStatus to use the
shared st-warn warning palette for pill, matching its existing dot and ring
tokens; leave the other status properties unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Pro Plus
Run ID: 217e5ec9-8e44-484f-8308-5c4e7033d32c
📒 Files selected for processing (7)
src/components/pages/LanguagesPage.tsxsrc/components/pages/MapPage.tsxsrc/components/pages/ProjectPhasesTab.tsxsrc/components/pages/map/FieldMapPanel.tsxsrc/components/pages/map/ProjectPopupContent.tsxsrc/constants/phaseStatus.tssrc/index.css
…ng its projects Deactivation is a platform-admin action gated by the confirmation dialog, not by usage: the API (tripod-api #97) allows it while projects still reference the language. The dialog keeps listing those projects so the admin decides with the usage in front of them, and the confirm button is no longer disabled when the language is in use. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/pages/LanguagesPage.tsx (2)
228-245: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse
displayLanguagesfor the empty-state check.When no active languages exist, enabling “Include inactive” can populate
allLanguageswhilelanguages.lengthstays zero. The page then continues rendering “No languages yet,” preventing platform admins from seeing or restoring inactive languages.Proposed fix
- languages.length === 0 ? ( + displayLanguages.length === 0 ? (🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/LanguagesPage.tsx` around lines 228 - 245, Update the empty-state condition in the languages view to check displayLanguages.length instead of languages.length. Keep displayLanguages as the source for determining whether the table or EmptyState renders, so enabling showInactive exposes inactive languages and preserves the existing empty-state behavior when none are available.
56-61: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winDo not convert project-load failures into zero project counts.
This request feeds every row’s project count and the deactivation fallback list. On failure,
setProjects([])makes an outage look like “no projects,” potentially hiding affected projects. Track the error and render an unavailable state or retain clearly marked last-known data instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/pages/LanguagesPage.tsx` around lines 56 - 61, Update the project-loading flow in LanguagesPage’s useEffect so a projectsAPI.list failure is not converted into an empty projects array. Track the load error and render project counts and the deactivation fallback using an unavailable state, or retain clearly marked last-known projects, while preserving successful data handling through setProjects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/components/pages/LanguagesPage.tsx`:
- Around line 228-245: Update the empty-state condition in the languages view to
check displayLanguages.length instead of languages.length. Keep displayLanguages
as the source for determining whether the table or EmptyState renders, so
enabling showInactive exposes inactive languages and preserves the existing
empty-state behavior when none are available.
- Around line 56-61: Update the project-loading flow in LanguagesPage’s
useEffect so a projectsAPI.list failure is not converted into an empty projects
array. Track the load error and render project counts and the deactivation
fallback using an unavailable state, or retain clearly marked last-known
projects, while preserving successful data handling through setProjects.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Pro Plus
Run ID: 6101fb01-8851-4f83-936c-3e7a18ef1656
📒 Files selected for processing (1)
src/components/pages/LanguagesPage.tsx
Every arbitrary px length in the components and in the app stylesheet is now expressed in rem, the root font-size is set to 125% and the Tailwind breakpoints are scaled by the same factor so the layout still switches at the same physical widths. The console at 100% browser zoom now matches the density the design was reviewed at under 125% zoom. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The flow graph is an SVG laid out in fixed user units, so it does not follow the rem-based UI scale. It now reads the scale from the root font-size and applies it to the canvas height and to the fit-to-view zoom cap. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Adopt the mock copy (Your profile / Save profile), switch the avatar actions to inline Upload photo and Remove links, and drop the file-type and size caption. The photo keeps the xl circle so it stays much larger than the 52px avatar in the mock. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The link-action variant of ImageUpload now renders a plain circle with no dashed ring and no hover overlay, and the profile dialog falls back to the shared avatar initials and palette when there is no photo. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Closes OBT-258.
Summary
Reimplements the console UI to match the approved Shema design prototype (
Tripod Console.dc.html), wired to the realtripod-api. The prototype runs on a mockdata.jsplus a customsc-if/sc-fortemplating runtime; this branch translates it into the real React 18 + TypeScript + Vite + Tailwind v4 front.No mock data — every screen binds to
tripod-apithrough the existingsrc/services/api.tsnamespaces and its JWT interceptors.This branch supersedes and contains
levigft/obt-259-...(public requests / admin review), so that branch does not need its own PR.Screens
Public request (
/request), Dashboard (My Apps + Platform Overview), Languages, Projects + Project detail (Info / Phases / Access, location map), Users + User detail, Manage Apps + App detail, Phases (catalog + dependency graph, admin-only), Map, and the shared chrome (sidebar rail, header, dialogs, toasts).Notable behaviour changes
/appshell requires platform admin or manager; anyone else getsAccessDeniedPage. Admin-only routes (/app/users,/app/apps,/app/phases) stay wrapped inAdminRoute.platformsarray (web / android / ios) with one badge per platform, replacing the singleplatformfield.Backend dependency
The project cards need
image_url,phases_completed,phases_total,members_previewandteam_sizeonProjectResponse. Those come from tripod-api PR #117 (OBT-262) — which already contains OBT-216'steam_size— plus its migration20260716_0002. Without that API, the Projects page renders but the progress bar and avatar stack have nothing to show.Upload fix included
userDetailuploaded admin-set photos withfolder=user-avatars, which is not in the API's allowlist, so they were silently stored under the genericimages/prefix instead of alongside the onesProfileDialoguploads. Now sendsavatars.Map popup and language usage (design follow-up)
PHASE_STATUS_CONFIGthat the project phases graph also uses. The card now follows the prototype'spopupHtml: plain location line, up to four chips (+Nfor the rest), no divider above the meta line, a pill "Open project" button, and the dark CARTO tiles in dark mode.—, so there was no way to see how many projects use a language. It now shows that count per row. The deactivate dialog is titled after the language and, when the language is in use, lists those projects in anaccent-softcallout so the platform admin confirms with the usage in front of them. Deactivation stays allowed — it is gated by the admin role, not by usage, matchingtripod-api#97.Test plan
tsc -bandnpm run lintpass clean.AccessDeniedPagewith the logout variant.avatars/./app/map: open a marker popup — the phase chips are tinted per status; change a phase status under Project detail → Phases, go back to the map and the chip color follows./app/languagesas a platform admin: the Projects column shows a count per language; deactivating a language that has projects lists them in the dialog and still goes through; deactivating an unused language works too. A manager never sees the deactivate action.🤖 Generated with Claude Code
Summary by CodeRabbit