Repository navigation
bug(plans): create-plan reported no success/error feedback (QA standard user) — verify vs deploy lag #949
Description
Summary
QA (standard-user session) reported that creating a Purchase Plan produces no result and no error/success feedback. Covers two QA sheet rows with the same root cause.
Steps / Expected / Actual
Row 547 — Plan from Opportunities
- Action: On Opportunities, select a commitment, click "Plan from 1 selected".
- Expected: Plan is created.
- Actual: Nothing happens; the Create Purchase Plan modal stays open; no success message.
Row 548 — New Plan from Plans page
- Action: On Plans page, click "New Plan", configure details, submit.
- Expected: Plan is created.
- Actual: Same; nothing happens; modal stays open; no success/error feedback.
Investigation against current base (feat/multicloud-web-frontend @ #883)
Read the full create-plan path FE + BE on the current base:
- Frontend
savePlan(frontend/src/plans.ts:1099): wraps the create call intry/catch. On success it closes the modal, reloads plans, and shows aPlan created successfullytoast. On any error it keeps the modal open and shows aFailed to save plan: <message>toast.showToastis imported and wired. apiRequest(frontend/src/api/client.ts:253): on a non-2xx response it parses the JSON body, reads theerrorfield, and throws anErrorcarrying that message (and structureddetails).- Backend
createPlan(internal/api/handler_plans.go:65) +handleRequestError(internal/api/handler.go:372): every failure path returns a JSON body{"error": "..."}—NewClientErrorfor 400/401/403/404 and{"error":"Internal server error"}for the generic 500 case.
Permission gate is NOT the cause. createPlan requires create:plans. DefaultUserPermissions() (internal/auth/types.go:466) grants standard users create:plans and update:plans. The Purchaser-group work (#923/#924) only carves execute / approve-any / retry-any on purchases out of the admin wildcard; it does not touch create:plans. So a standard user is authorized to create plans on the current base.
Conclusion: On the current base the create-plan flow is fully wired for feedback. Success shows a toast and closes the modal; any error (including 403 / 400 / 404 / 500) keeps the modal open and shows a toast with the backend message. There is no silent-failure code path. The savePlan error toast has been present since 2026-03-23 (0ca70ef48).
Reproduces for: neither admin nor standard user on the current base — both get explicit toast feedback.
Most likely cause of the QA observation
Deploy lag. #907/#912 (group-only authz) and #924 (Purchaser group) only reached prod in today's deploy; the QA session very likely ran against a frontend bundle and/or backend that predates the wired-up feedback path or hit a transient backend error. The deployed frontend bundle is built and shipped from this branch, so a stale CDN/bundle can present old behavior even after the backend updates.
A related, already-tracked structural defect is #944 (createPlan returns an opaque 500 with no structured log), which has open PR #946 (fix/create-plan-500-observability). That improves backend root-causing but the user-facing feedback is already correct on the current base.
Recommended action
- Re-test on the current deploy (post today's deploy) as both admin and standard user. Capture the browser Network tab for
POST /api/plans(status + response body) and the toast, if any. - If the re-test still shows no toast, this becomes a live frontend bug — attach the captured request/response and reopen scope; otherwise close as deploy-lag.
- Land fix(api/plans): differentiate ErrNotFound->404 + add structured slog on createPlan errors (closes #944) #946 for backend observability so any future 500 is diagnosable from logs.
No deploy-independent code fix is warranted from the current-base reading: the feedback path is correct and standard users are authorized.
Activity
cristim commentedon Jun 5, 2026
Fixed in #987.
Root cause confirmed as deploy-independent: the create-plan-from-commitment flow could not pre-fill the Target Account for Standard / Read-Only users because
prefillAccountChipFromIdfetched the account viaGET /api/accounts/:id(gated onview:accounts), which 403s for those users. The 403 was swallowed by the prefill's catch, leaving the Target Accounts list empty, so the empty-target guard insavePlanrejected submit — and although the guard does show a toast, the combination presented as 'nothing happens' for rows 547/548.#987 wires the prefill (and the plan-account search) to the new minimal-disclosure
api.listAccountsMinimal()(view:recommendations), so the target now pre-fills for Standard users and the plan saves. The save-failure path (error toast + modal stays open) is already correct and now has an explicit regression test asserting both halves.fix(accounts): least-privilege accounts list for standard-user filter…