Add Observation Workspace - #91
Conversation
Reviewer's GuideImplements an Observation Workspace master-detail UI for notes and corresponding backend aggregate endpoints, reusing existing note domain/query services while adding workspace-specific DTOs, timeline composition, allowed-actions logic, and responsive RTL styling. Sequence diagram for loading and acting on a note in the Observation workspacesequenceDiagram
actor Operator
participant ObservationWorkspacePage
participant ApiClient as api_notes_workspaceDetail
participant ApiEndpoints as NotesEndpoints
participant WorkspaceService as NoteWorkspaceQueryService
participant NoteQueries as NoteQueryService
Operator->>ObservationWorkspacePage: selectNote(id)
ObservationWorkspacePage->>ApiClient: workspaceDetail(id)
ApiClient->>ApiEndpoints: GET /api/v1/notes/{id}/workspace
ApiEndpoints->>WorkspaceService: GetAsync(id, ct)
WorkspaceService->>NoteQueries: GetDetailAsync(id, ct)
WorkspaceService->>NoteQueries: GetAssignmentsAsync(id, ct)
WorkspaceService->>NoteQueries: GetHistoryAsync(id, ct)
WorkspaceService-->>ApiEndpoints: NoteWorkspaceDetailDto
ApiEndpoints-->>ApiClient: NoteWorkspaceDetail
ApiClient-->>ObservationWorkspacePage: NoteWorkspaceDetail
ObservationWorkspacePage->>ObservationWorkspacePage: render ActionBar(allowedActions)
Operator->>ObservationWorkspacePage: click inline action
ObservationWorkspacePage->>ApiClient: submit(id, reason, rowVersion)
ObservationWorkspacePage->>ApiClient: startWork(id, reason, rowVersion)
ObservationWorkspacePage->>ApiClient: submitForVerification(id, reason, rowVersion)
ObservationWorkspacePage->>ApiClient: returnForRework(id, reason, rowVersion)
ObservationWorkspacePage->>ApiClient: reopen(id, reason, rowVersion)
ObservationWorkspacePage->>ApiClient: cancel(id, reason, rowVersion)
ApiClient->>ApiEndpoints: POST /api/v1/notes/{id}/command
ObservationWorkspacePage->>ApiClient: workspace(filters)
ObservationWorkspacePage->>ApiClient: workspaceDetail(id)
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Warning Review limit reached
Next review available in: 39 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughAdds a notes workspace with backend list/detail endpoints, aggregated workspace DTOs, server-derived actions and timeline data, a responsive frontend master-detail page, workflow action handling, workspace styling, and interaction tests. ChangesObservation workspace
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Operator
participant ObservationWorkspacePage
participant NotesApi
participant NoteWorkspaceQueryService
participant Database
Operator->>ObservationWorkspacePage: open notes workspace and apply filters
ObservationWorkspacePage->>NotesApi: request workspace list
NotesApi->>NoteWorkspaceQueryService: ListAsync
NoteWorkspaceQueryService->>Database: query notes
Database-->>NoteWorkspaceQueryService: return paged notes
NoteWorkspaceQueryService-->>NotesApi: return workspace list
NotesApi-->>ObservationWorkspacePage: render note cards
Operator->>ObservationWorkspacePage: select a note
ObservationWorkspacePage->>NotesApi: request workspace detail
NotesApi->>NoteWorkspaceQueryService: GetAsync
NoteWorkspaceQueryService->>Database: load related data and history
Database-->>NoteWorkspaceQueryService: return workspace details
NoteWorkspaceQueryService-->>NotesApi: return detail and allowed actions
NotesApi-->>ObservationWorkspacePage: render detail tabs and actions
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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.
Hey - I've found 3 issues, and left some high level feedback:
- In
NoteWorkspaceSummaryDtoconstruction,WaitingVerificationandWaitingClosureApprovalare both set tonote.Status == NoteStatus.PendingVerification, which makes the two flags indistinguishable; consider separating their conditions or dropping one until a distinct state exists. - The frontend action bar renders buttons for
REQUEST_RESOURCE(and other non-inline actions) based onallowedActions, but the mutation handler throws for these, resulting in a generic error; either omit such actions fromallowedActionsuntil backed by endpoints or route them to dedicated forms/pages to avoid confusing operators.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `NoteWorkspaceSummaryDto` construction, `WaitingVerification` and `WaitingClosureApproval` are both set to `note.Status == NoteStatus.PendingVerification`, which makes the two flags indistinguishable; consider separating their conditions or dropping one until a distinct state exists.
- The frontend action bar renders buttons for `REQUEST_RESOURCE` (and other non-inline actions) based on `allowedActions`, but the mutation handler throws for these, resulting in a generic error; either omit such actions from `allowedActions` until backed by endpoints or route them to dedicated forms/pages to avoid confusing operators.
## Individual Comments
### Comment 1
<location path="src/backend/Baseera.Application/Notes/NoteWorkspaceQueryService.cs" line_range="67" />
<code_context>
+ openActions,
+ attachmentRows.Count,
+ note.Status == NoteStatus.InProgress && openActions > 0,
+ note.Status == NoteStatus.PendingVerification,
+ note.Status == NoteStatus.PendingVerification,
+ false,
</code_context>
<issue_to_address>
**issue (bug_risk):** Summary flags `WaitingVerification` and `WaitingClosureApproval` are currently identical, which likely misrepresents state.
In `NoteWorkspaceSummaryDto`, both `waitingVerification` and `waitingClosureApproval` are set from `note.Status == NoteStatus.PendingVerification`, making them indistinguishable. This can break or confuse any UI that relies on these flags to represent different states. Please either derive `waitingClosureApproval` from a distinct condition (e.g., additional closure-approval requirement or a separate status) or remove/merge the flag until you have a clearly enforced distinction in the domain model.
</issue_to_address>
### Comment 2
<location path="src/frontend/src/api/client.ts" line_range="252" />
<code_context>
+ descriptionAr?: string | null
+ actorDisplayName?: string | null
+ occurredAtUtc: string
+ tone: string
+}
+
</code_context>
<issue_to_address>
**suggestion:** Use a literal union type for `tone` to align with the constrained server values and catch typos at compile time.
On the backend, `tone` only uses specific values (`"danger"`, `"ok"`, `"info"`, `"muted"`, etc.), but the TS client currently types it as `string`, losing type safety and autocomplete. Consider defining:
```ts
export type NoteWorkspaceTimelineEntry = {
// ...
tone: 'danger' | 'ok' | 'info' | 'muted' | 'warn'
}
```
and keeping this union aligned with the server and CSS. Similarly, `allowedActions: string[]` in `NoteWorkspaceDetail` could be a union of known action codes to avoid client/server mismatches.
Suggested implementation:
```typescript
export type NoteWorkspaceTimelineEntry = {
id: string
type: string
titleAr: string
descriptionAr?: string | null
actorDisplayName?: string | null
occurredAtUtc: string
tone: 'danger' | 'ok' | 'info' | 'muted' | 'warn'
}
export type NoteWorkspaceSummary = {
```
1. Update `NoteWorkspaceDetail` (or the relevant type that exposes `allowedActions: string[]`) to use a string literal union instead of plain `string[]`, e.g.:
```ts
type NoteWorkspaceAllowedAction = 'close' | 'escalate' | 'reopen' | 'assign' // etc., aligned with backend
export type NoteWorkspaceDetail = {
// ...
allowedActions: NoteWorkspaceAllowedAction[]
}
```
2. Ensure the union values stay in sync with the backend enum/constant list and any CSS or UI conventions (e.g. if backend adds a new action, update this union accordingly).
</issue_to_address>
### Comment 3
<location path="docs/observation-reference-gap-analysis.md" line_range="187-188" />
<code_context>
+- Collapsed list: `56px`.
+- Card: `min-height: 96px`, `max-height: 132px`, padding `12px`, gap `8px`.
+- Filter bar: `44px` controls, wrapping on tablet.
+- Detail header: sticky top inside workspace, `120-170px` depending badges.
+- Mobile: list first; selecting a note hides list and shows detail full width with a back button.
+
</code_context>
<issue_to_address>
**nitpick (typo):** Add "on" for correct phrasing in "depending badges".
The phrase "`120-170px` depending badges" is missing a preposition; please change it to "`120-170px` depending on badges".
```suggestion
- Filter bar: `44px` controls, wrapping on tablet.
- Detail header: sticky top inside workspace, `120-170px` depending on badges.
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/backend/Baseera.Application/Notes/NoteWorkspaceQueryService.cs`:
- Around line 41-52: Update NoteWorkspaceQueryService’s corrective-action
retrieval before BuildTimelineAsync so the timeline receives all corrective
actions, not the Actions tab’s capped first page. Remove the fixed Page/PageSize
limitation or use the repository’s unbounded/full-list query mechanism, then
pass the complete collection to BuildTimelineAsync while preserving the existing
sorting and cancellation behavior.
In `@src/frontend/src/pages/notes/ObservationWorkspacePage.tsx`:
- Around line 216-223: Add a stable remount key to the WorkspaceDetail element
based on the selected note identifier, so changing selectedId resets
WorkspaceDetail and ActionBar local state before rendering the newly selected
note. Keep the existing props and navigation behavior unchanged.
- Around line 75-81: Update the debounce effect in ObservationWorkspacePage so
it does not reset page on the initial mount, preserving deep-linked pagination
from the URL. Track initial render with the component’s existing lifecycle
state, then apply setPage(1) and setDebouncedSearch only when searchInput
changes after mount; keep the 300ms debounce and cleanup behavior intact.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5c07e29c-3072-42d6-8e39-06c3d177b8d3
⛔ Files ignored due to path filters (3)
docs/screenshots/observation-workspace-desktop.pngis excluded by!**/*.pngdocs/screenshots/observation-workspace-mobile.pngis excluded by!**/*.pngdocs/screenshots/observation-workspace-tablet.pngis excluded by!**/*.png
📒 Files selected for processing (10)
docs/observation-reference-gap-analysis.mdsrc/backend/Baseera.Api/Endpoints/ApiEndpoints.cssrc/backend/Baseera.Application/DependencyInjection/ApplicationServiceCollectionExtensions.cssrc/backend/Baseera.Application/Notes/NoteWorkspaceDtos.cssrc/backend/Baseera.Application/Notes/NoteWorkspaceQueryService.cssrc/frontend/src/App.tsxsrc/frontend/src/api/client.tssrc/frontend/src/index.csssrc/frontend/src/pages/notes/ObservationWorkspacePage.test.tsxsrc/frontend/src/pages/notes/ObservationWorkspacePage.tsx
|



Summary
Architecture
Reference Usage
Screenshots
Verification
Notes / Remaining Work
Relates #64 #65 #66 #67 #68 #69 #70 #71 #72 #73 #74 #75 #76 #77 #78 #79 #80 #81 #82 #83 #84 #85
Summary by Sourcery
Introduce a unified Observation Workspace master-detail experience for notes, backed by new workspace aggregation APIs and query services, and wire it into the main navigation as the primary notes view.
New Features:
Enhancements:
Documentation:
Summary by CodeRabbit
New Features
Documentation
Tests