Fix UX feedback across Review, Intake, Dashboard, Document, Consultation, Response (FEEDBACK-01..10)#3
Merged
Merged
Conversation
Each document row in /review now navigates to the detail page on click (mouse or keyboard Enter/Space), instead of requiring a small 'View' button in the last column. The last column shows a chevron indicator on hover. Existing E2E helper updated to click the row; new E2E test added to verify clicking the title cell navigates correctly. Made-with: Cursor
Replaced the bare spinner on /intake with a 4-step progress stepper (Upload -> Validate -> Extract -> AI analyze) that advances in real time while the upload request is in flight and finalises based on the concrete artifact + analyses contained in the server response. On success the page shows a prominent call-to-action linking directly to the created document's review case (/documents/<id>), plus a secondary link to the review queue and an 'Upload another' action. Typed uploadDocument<T>() in web/src/lib/api.ts to preserve the response shape consumed by the intake stepper. Made-with: Cursor
Introduced web/src/lib/format.ts with a formatBytes() helper (binary units, 1 KB = 1024 B, up to PB). The DocumentDetailPage file metadata block now renders the size in a human-readable unit (e.g. '5.1 MB'), while still exposing the exact byte count inline for audit purposes. Covered with Vitest unit tests and a new Playwright E2E that asserts the UI shows both a unit label and the raw byte count. Made-with: Cursor
…FEEDBACK-05)
Rewrote the /consultation page to behave like a proper chat app:
- Messages are now sorted by created_at ascending so new notes always
appear at the bottom of the thread regardless of role or send order
(fixes the 'added at different positions' bug).
- The active user's messages render on the right in a blue bubble;
other roles render on the left in a white bubble with a role-coloured
badge. Each bubble carries data-author-role / data-own attributes for
tests and styling.
- Reviewers and supervisors continue to use the workflow-aware
/documents/{id}/request-consultation endpoint when the doc is not
yet in consultation; consultants (and everyone once the thread is
open) use the simpler /consultation/{id}/notes endpoint so all three
participating roles can reply from the same composer.
- The composer now displays the active role, inline API errors are
rendered in the thread, and roles that cannot participate
(Intake Clerk) see a clear lock message instead of a broken input.
Also fixed a pre-existing crash in workflow.add_consultation: the
import 'from app.repositories import document as doc_repo' was being
shadowed by the CRUD instance re-exported from the package __init__,
so doc_repo.document.get(...) raised AttributeError. Switched to
explicit 'from app.repositories.document import document as
doc_repo_document, ...' which also fixes the 500 on POST
/consultation/{doc_id}/notes.
Added a Playwright test that verifies bubble sides, chronological
order across two roles, consultant reply, and the read-only state
for Intake Clerk.
Made-with: Cursor
- Add a visual WorkflowPipeline on the document detail page so any role can see where the case sits in the canonical lifecycle (received → extracted → analyzed → routed → under review → in consultation → approved → closed). - Add WorkflowContext that prints the acting role, current status, a concise "what happens next" hint, and a "waiting on another role" banner when the current role cannot act but someone else can. - Split workflow actions into "Next steps for you" (currently available) and a collapsible "Actions not available right now" disclosure that explains why each disabled action is blocked. - Extend workflow-actions helpers with PIPELINE_STAGES, getPipelineIndex, ROLE_LABEL, getResponsibleRoles, and getNextStepHint. - Fix ui-card so Card/CardHeader/CardTitle/CardContent forward extra HTML attributes (data-testid, etc.) — previously they were dropped, which broke the new panels for automated tests. - Raise intake upload E2E timeout to 60s since the upload fans out to four synchronous LLM calls whose latency varies. - Add a dedicated Playwright test that verifies the pipeline, role context, waiting-on banner (Intake Clerk), and disabled-actions disclosure (Supervisor), and tighten Step 9b to pick an in-consultation thread. Made-with: Cursor
…ACK-07) - Sidebar: search box, Active/Resolved groups with counts, richer cards (status pill with icon, relative timestamp, last-message snippet with author, urgency badge), sticky and independently scrollable column. - Chat bubbles: max-w cap 75%, break-words + whitespace-pre-wrap so long messages wrap inside the bubble instead of clipping. - Composer: textarea with Enter-to-send / Shift+Enter newline hint, role chip stays aligned, auto-scroll-to-bottom when notes update so the composer no longer covers new replies. - Response page: mirror the same sidebar polish (search, Pending / Dispatched groups, relative timestamps, urgency badge). - Add Playwright 'Step 9d' covering sidebar structure, search filter, textarea composer, bubble wrap + auto-scroll. - Fix BUG-004 test to use [data-testid=consultation-thread] (header no longer contains the literal 'Thread:' prefix). - Broaden FEEDBACK-06 test's doc filter to canonical pipeline statuses only (the demo seed includes error states like 'analysis_failed'). Made-with: Cursor
Adds server-side offset/limit pagination to `GET /documents/` with a `X-Total-Count` response header, wires up a paged `apiGetPaged` helper in the web client, and rewrites the review page around an IntersectionObserver sentinel that fetches the next page when the user scrolls near the bottom. Search is now debounced and sent to the backend via the `q=` query param. - Backend: `DocumentRepository` gains `count()` + `offset=` on `list()`; the endpoint exposes `X-Total-Count` (also whitelisted on CORS). - Web: new `apiGetPaged<T>` helper returns items + total header value. - Review page: replaces eager load with 25-row pages, footer shows "Showing N of M", sentinel auto-loads more, "Load more" button as a fallback, and the search input talks to the backend. Made-with: Cursor
…debars (FEEDBACK-09) Before this change the outer page scrolled because the sidebar grew taller than the thread pane when there were many threads. Now each page is a fixed `h-[calc(100vh-8rem)]` flex container with two internally-scrolling columns, so the browser never gets an outer scrollbar. The sidebars also render incrementally: only 15 cards are mounted up front, and an IntersectionObserver sentinel grows the window by 15 at a time as the user scrolls — keeping the DOM light on large demos. Made-with: Cursor
Dashboard - Stat cards: trend is now a colored pill (emerald/red) rather than bare text, the label and value sit at the card bottom so the four cards have matching internal rhythm. - Chart cards: the two cards are grid-stretched to equal height; the inner surrounded block fills that height instead of double-padding via `m-6 p-6`, which previously made the donut card taller. - Status bars + donut legend: numeric values use `tabular-nums` and a fixed-width right-aligned column so bars/labels line up regardless of digit count. Long labels truncate gracefully. Intake - Dropzone and guidelines cards now share the same height via `items-stretch` + `flex flex-col`. The dropzone fills its Card with `flex-1 min-h-[260px]` instead of hard-coded padding, so the two cards always match visually when the page is idle. Made-with: Cursor
- New Step 9e verifies /review lazy-loads more rows when the sentinel enters the viewport, and that /consultation and /response do NOT produce an outer page scrollbar while their sidebars remain internally scrollable. The test seeds a handful of lightweight .txt uploads via API when the DB doesn't already have enough rows to exercise pagination. - Bumps per-test timeouts on AI-heavy steps (3, 3a, 5, 6, 7) to 120s so variability in synchronous LLM calls no longer flakes the suite. Made-with: Cursor
… flash (FEEDBACK-11)
Clicking "Reroute document" and picking a department silently worked
on the backend but felt like a no-op in the UI: after reroute the
reviewer role's GET /documents/{id} implicitly flips status from
`routed` back to `under_review` (by design), so the status pill
never changes, and the page did not show the assigned department
anywhere in the header or metadata. Users saw "nothing happened".
Changes:
- DocumentDetailPage: new "Assigned to: <dept>" line under the title
(with a building icon and an italic "Not yet assigned" fallback),
fed by `doc.assigned_department_id` + `departmentNames` — so any
reroute is immediately visible after the silent refetch.
- Add a dismissible success/error flash banner (5s auto-hide) shown
after every workflow action. For reroute specifically it reads
"Document rerouted to <dept name>." using the picked department.
Replaces the previous bare `alert()` on failure too.
- New e2e spec (`reroute-visibility.spec.ts`) drives the whole flow
against a real backend: picks a reroutable doc + target dept via
API, opens the detail page as Department Reviewer, triggers the
reroute, and asserts the assigned-to line updates and the success
flash mentions the new department.
Made-with: Cursor
After a reroute, the page landed the user back on `under_review` with the action buttons repainted — but with no signal that *progressing* the workflow now belongs to a Supervisor (reviewers cannot close). Users asked "after reroute, how do I go to the next step?". Added two things to make that obvious: 1. **Latest routing callout** — sits at the top of the Workflow actions card. Shows decision (Rerouted / Accepted), the final department, the rationale, and who decided it and when. Makes the causal chain visible instead of forcing users to expand the collapsed routing history block. 2. **Next owner(s) for this document** — groups the actions that would be available to *other* roles on this same status, with a "Switch role" button per group. Clicking it rewrites `govdoc_role` in the RoleProvider/localStorage, so the whole page re-renders as that role and the correct set of actions (e.g. "Close document" for Supervisor) is immediately revealed. Supporting changes: - `workflow-actions.ts`: new `getActionsAvailableForOtherRoles()` helper and `toFrontendRoleLabel()` inverse of `toRoleId()`. - `DocumentDetailInner`: pulls `setRole` from the role hook and wires the switch button through it. - `reroute-visibility.spec.ts`: extends the Playwright spec to assert the callout shows the final dept + decided-by role, the "next owner" block lists Supervisor with `Close document`, and clicking "Switch role" moves the acting-as chip to Supervisor and reveals the `Close document` action. Made-with: Cursor
…il (FEEDBACK-13)
The previous "Next steps for you" list rendered every available
action with equal visual weight, grouped by their internal taxonomy
(Review / Consultation / Closeout). After a reroute the Supervisor
landed on the doc seeing five buttons (Reroute, Escalate, Request
consultation, Mark out of scope, Close) with no signal which one
actually advances the pipeline — the user asked "how do I move
forward?". The "What happens next" hint also said "A Reviewer
must..." even when the active role was already Supervisor.
Changes:
- `workflow-actions.ts`:
- New `FORWARD_ACTION_BY_STATUS_ROLE` map defining the single most
progressive action per (status, role): approve-routing, close,
resolve-consultation, etc. Exposed via `getForwardActionId` and
`partitionByForwardness` helpers.
- `getNextStepHint` now takes an optional `role` and returns
role-aware copy for under_review/analyzed/in_consultation/approved
statuses. Default fallback unchanged for backwards compat.
- `getActionsAvailableForOtherRoles` now only reports a role if
*that* role has a forward action for the current status (e.g.
reviewer sees "Supervisor can Close document" but supervisor no
longer sees "Reviewer can Reroute" — that's a loop, not a handoff).
- `DocumentDetailPage.tsx`:
- "Next steps for you" replaced by a two-tier layout: a green
"RECOMMENDED NEXT STEP" card with an explanation sentence and
the single forward button, followed by a muted "Or, alternatives"
list for reroute / request-consultation / mark-out-of-scope.
When no forward action exists for the current role the block
degrades to "Available actions" and the other-roles handoff CTA
takes over.
- `escalate` is hidden when the active role is already Supervisor
(self-escalation was a no-op).
- "Next owner(s)" CTA now only renders when `getForwardActionId`
returns null for the active role — i.e. the user genuinely needs
to hand off to make progress. Reduces noise for supervisors.
- Each other-role card now says "Can <Forward action> to move the
workflow forward" instead of listing every branch action.
- Tests:
- `reroute-visibility.spec.ts` extended: after switching to
Supervisor it asserts the forward-action card promotes Close,
that the other-roles CTA disappears, that Escalate is no longer
visible, and that the hint no longer reads "A Reviewer must...".
- `govdoc.spec.ts` Step 9c updated to accept either "Recommended
next step" or "Available actions" in the available-actions block.
Made-with: Cursor
…K-13) Resolving a single consultation note used to unconditionally flip the document to under_review, even if sibling notes were still open. That stranded documents with multiple parallel consultations in an inconsistent state (status=under_review while open notes remained), hiding the "Resolve consultation" action from the UI. Now the transition only happens once the last open note is resolved. Covers the broken state seen on ac153273-... in QA. Made-with: Cursor
…EDBACK-13) Documents that drifted into a non-in_consultation status while still holding open notes (legacy data from the pre-fix resolve-consultation bug) now show an amber warning banner at the top of the document detail page. The banner explains the situation and links to /consultation?doc=<id>, which now honors the doc query param to auto-select the right thread. Made-with: Cursor
Uploads a fresh document, drives it to in_consultation via two parallel notes, then asserts: - resolving the first note keeps status=in_consultation - resolving the second note flips it to under_review Locks in the backend fix for ac153273-... style drift. Made-with: Cursor
The consultation page used to be chat-only, forcing consultants to jump back to /documents/<id> just to mark a note resolved. That round-trip caused drift: many documents accumulated open notes because no one bothered to navigate away. Now each open note shows a "Mark as resolved" button, visible only to the role named in the note's target_role (and not to the author). The button hits the existing resolve-consultation endpoint, so once the last open note is resolved the document flips to under_review (the backend fix from 3c11c99 keeps sibling notes sticky). A new footer hint under the composer explains the current state: - N notes waiting for YOUR reply - waiting on N open notes from other roles - thread closed → open the document page for next-step actions Approve / reroute / close actions deliberately stay on the document detail page since they are document-level decisions. Made-with: Cursor
Root cause for documents stuck on under_review with no owner: the approve-routing endpoint updated status -> routed but never copied the AI's suggested_department_id onto the document, and never finalized the routing_decision row created at analysis time. Supervisors then saw "Close document" as the recommended next step on orphaned docs. Fix approve-routing to: - Look up the AI-produced routing_decision (accepted, final=None) - Set final_department_id = suggested_department_id - Set decided_by_role = current role - Copy the suggestion onto document.assigned_department_id - Refuse with NO_AI_ROUTING if there is no AI suggestion to accept Adds integration tests for both the happy path and the guard, plus an e2e regression that uploads -> waits for analysis -> approves routing and verifies the full populated state. Made-with: Cursor
When a supervisor opens a doc that is under_review but has no owning department, "Close document" is the wrong forward action — closing a docless doc just hides an un-owned case. Instead: - getForwardActionId + getNextStepHint accept a DocContext so they can see assigned_department_id and recommend "Reroute document" with a tailored hint for supervisors/reviewers in that state. - DocumentDetailPage gains a NoAssignedDepartmentBanner that surfaces the missing-owner situation right under the flash area on any non-terminal routed/under_review/in_consultation doc without a dept. - forwardExplanation gets a "reroute" case. - ConsultationPage header pill now correctly says "1 note" (singular) instead of "1 notes". Unit tests cover the new (status, role, dept) combinations and the string-arg legacy overloads. The corresponding e2e regression lives in e2e/approve-routing-assigns-dept.spec.ts. Made-with: Cursor
Add POST /documents/{id}/approve for reviewers and supervisors, moving the
document to `approved` from `under_review` or `in_consultation`. Reject with
OPEN_CONSULTATION_NOTES when any note is still unresolved.
POST /close now only performs `approved` → `closed` (supervisor-only).
Grant documents.approve to reviewer and supervisor in roles.yaml.
Add integration tests, RBAC coverage, and contract assertion that clerks
cannot approve.
Made-with: Cursor
Add workflow action `approve` (reviewer + supervisor) with forward-action priority over close on under_review. Close is only available in `approved` status for supervisors. Pass assigned_department_id into getActionsAvailableForOtherRoles so supervisor handoff hints stay correct. Response queue "Approve & Close" chains POST /approve then POST /close. Update unit tests, govdoc Step 8 e2e, and reroute-visibility (consultant view for next-owner CTA now that reviewers have a forward action). Made-with: Cursor
`closed` is terminal for actions but was blanking the whole pipeline because `terminal` suppressed all done/active states. Add getPipelineStepState() so closed shows every stage as completed. When status is approved without any consultation activity, keep the In Consultation row pending so we do not imply a branch that never happened. Pass hadConsultationActivity from the document detail page (notes exist or currently in_consultation). Made-with: Cursor
…ehind nginx
- Parse `{ error: { message } }` from custom FastAPI JSONResponse handlers;
the client only looked at `detail`, so most failures showed as the useless
"API request failed" string.
- On non-JSON error bodies (e.g. 413 HTML), include HTTP status and a body
snippet in the thrown message.
- Raise nginx client_max_body_size to 100m and extend proxy timeouts so
uploads and AI intake are not cut off at the default 1m / 60s limits.
Made-with: Cursor
Success flash now includes a button-style link to /consultation?doc=<id>, clearer copy, and a short note that the thread also appears on the document page. Extends flash timeout when a follow-up link is shown. Made-with: Cursor
…tion card The success flash alone was easy to miss (viewport scroll). Add persistent actions on the Consultation thread card: deep-link to /consultation?doc=, link to Review queue, and short copy. Scroll the flash banner into view when a consultation follow-up link is shown. Made-with: Cursor
Users confused closing from Review with seeing an outcome on Response. Add an on-page explainer and clearer empty state: Pending vs Dispatched, that closed items move to Dispatched, and which statuses appear in the queue. Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
… with link until dismissed Made-with: Cursor
Made-with: Cursor
…osed) Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two rounds of UX fixes addressing ten feedback items across the web UI.
Each is shipped as an independent commit with deploy + Playwright
coverage.
Round 1 (FEEDBACK-01..07)
navigates to the document detail page (no more hidden "View" button);
keyboard-accessible with
role="link"+ Enter/Space.with a live stepper (upload → validate → extract → analyze) and added
a prominent "Open review case" link once processing completes.
formats bytes as B / KB / MB / GB with the raw byte count in
parentheses; reusable
formatBytesutil + unit tests.chronologically, own messages render on the right, others on the
left, roles that can't reply see a read-only composer. Also fixed an
AttributeErrorinworkflow.add_consultationcaused by an importshadowing the
documentmodule.detail sidebar with a visual pipeline, role/status context panel,
"next steps for you" group, and a collapsible "actions not available
right now" list that explains why each action is disabled. Also
fixed a silent bug in the shared
Cardcomponents that weredropping
data-testidand other HTML attributes.both
/consultationand/responsewith search, status groups,status pill + icon, relative timestamps, last-message snippet,
urgency badge. Chat bubbles wrap long text, composer is an
auto-sizing textarea with "Enter to send · Shift+Enter for newline",
and the thread auto-scrolls to the bottom.
Round 2 (FEEDBACK-08..10)
/review: backendGET /documents/now supportsoffset+limitand returnsX-Total-Count; the review page renders 25 rows at a time, debouncessearch to the server, and loads more via an IntersectionObserver
sentinel (with a "Load more" button fallback and end-of-queue state).
and Response: both pages are now fixed to the viewport
(
h-[calc(100vh-8rem)]) with two internally-scrolling columns, sothe browser never gets an outer scrollbar. Sidebars mount 15 threads
initially and grow by 15 as the user scrolls the sidebar itself.
labels and values to the bottom, trend indicators become
emerald/red pills; the two dashboard charts sit in an
equal-height grid and use
tabular-numsfor right-aligned numericcolumns; intake dropzone and guidelines cards stretch to equal
height.
Commits (most recent last)
Test plan
make ci— 51 web unit tests, 227 backend testsnpx playwright test— 17 E2E including Steps 3a/3b/3c (intakestepper, review-row clickability, human-readable file size),
9b/9c/9d (two-sided chat, workflow guide, sidebar polish), and
new 9e (pagination + in-place scrolling on Consultation/Response)
Consultant, Supervisor) after every deploy