Skip to content

πŸš€ release(staging β†’ master): bilateral drawer and contribution, developers section, geography/MDS fixes, table resize-sort fix - #764

Merged
Cristian45 merged 27 commits into
masterfrom
staging
Sep 23, 2026
Merged

Cristian45 merged 27 commits into
masterfrom
staging

Conversation

@JuanGuzman-io

@JuanGuzman-io JuanGuzman-io commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Bilateral: read-only project summary and description in the setup drawer (P2-3756), the missing Contribution percentage in Project Information (P2-3760), Center Users can now edit the bilateral form (not only admins), responsive grid for people-trained fields in the capacity sharing drawer
  • Developers section: new API integration section and navigation, official Notion field docs as the primary reference, production bulk ingest pointed at the host that resolves, both Swagger actions use the outlined button
  • Results fixes: sub-national geography controls stay read-only once a result leaves Editing (P2-3788), MDS note at the top of General Information (P2-3286), Innovation Use drops the use level from the MDS and shows the three USD tables together (P2-3785), IPSR contributing dropdown excludes the package's own Science Program (P2-3746)
  • Tables and UI: phantom sort-click after a column resize is ignored in the results list and every other affected reporting-tool table; result-status chips all resolve through the shared token enum (P2-3704)
  • Archived spec and kaizen entry for bugfix/bilateral-section-autosave-on-navigate

27 commits, 81 files (+2925 / βˆ’225). staging merges into master with no conflicts.

Test plan

  • staging merges into master with no conflicts (checked with git merge-tree)
  • Each change carries its own Jest/lint evidence in its source branch
  • Smoke check on the staging deploy before promoting

πŸ€– Generated with Claude Code

yecksin and others added 27 commits September 21, 2026 16:11
…nd show the three USD tables together

Nicoleta Trifa, after a round of manual W3/bilateral testing (Freshservice #INC-163204, point 4).
Distributed by Juan Carlos Cadavid through Santiago SΓ‘nchez on 21-Sep-2026. Two of the four
sub-points; (b) and (d) are deliberately not in this commit, see below.

AC1 β€” the use level is no longer a minimum data standard ("the use levels are not part of the MDS
- pls remove"), reversing part of P2-3428. Removed in BOTH places that enforced it, because either
one alone produces a contradiction the reporter cannot act on: the form's tracker (a client-only
relaxation turns a blocked section into a green section whose Submit is still refused) and the
server gate, which answers `submit-for-review` AND `POST /api/bilateral/create` (a server-only
relaxation leaves the form still demanding it). The question stays on screen and is still stored
and returned β€” only the demand is gone, and it keeps driving the three questions below it. No
stored result becomes invalid: this validator only ever refused, it never wrote.

AC3 β€” the USD estimation shows its three destinations at once ("needs to have 3 fields: one for
the Program..., one for the bilateral project, one for the listed external partners"). All three
already existed since P2-3390, but Programs and Partners were rendered under the "Complete full
metadata" toggle, so a reporter who never opened it saw one of the three amounts they were asked
for. The three tables now render together above the toggle and the duplicate block below it is
gone β€” two `app-estimates-cgiar` bound to the same `body` would have drawn every row twice. Only
the W3/bilateral table stays required: investment is outside the green check by PO decision
(9-Sep-2026), and making the other two mandatory would have silently turned optional data into a
submit blocker.

Verified against the fix landed earlier today in 146d261: `reconcileInvestmentTables` already
re-reads all three arrays on entering the section, so the two tables promoted here inherit that
refresh instead of going stale β€” they were previously the ones most likely to.

Not in this commit, and why:
- (b) gender/youth "set-up as for pooled" is blocked on a data decision. The bilateral form asks
  "Sex and age disaggregated data available?" and stores `true` when the answer is YES, while the
  rest of the platform reads that same `result_actors.sex_and_age_disaggregation` column with the
  OPPOSITE meaning β€” pooled asks "Sex and age disaggregation does not apply"
  (`innovation-use-form.component.html:127`) and the validation SQL takes `= 0` as the branch that
  requires women/men and `= 1` as the branch that requires only `how_many`
  (`results-innovation-packages-validation-module.repository.ts:372-391`). Aligning the form with
  pooled therefore inverts what the column means for every bilateral actor already stored, in
  production since 18-Sep, and needs a backfill decision that is not ours to take.
- (d) the reporter's wording and the team's summary state opposite intents, and the symptom
  described matches the defect fixed today in 146d261 β€” it needs re-testing on this build before
  any code is written for it.

Tests: the two gates now carry their own locks, both verified to fail when the old rule is put
back. Server: tsc clean, 847 tests across 37 suites in api/bilateral, eslint clean on the files
touched. Client: 1760 tests across 52 suites in pages/bilateral.

πŸ›‘ NOT PUSHED: `npm run build:dev` β€” the only thing that typechecks Angular templates, and this
commit edits one β€” has not been run. 1.04 GB of RAM available at commit time, below the floor for
a build on this machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ram out of the contributing dropdown

The Innovation Package Contributors form offered the package's OWN Science Program as a
contributing one. Picking it answered 201 with a success toast and the chip was gone on
reload, because the server drops the owner without reporting it:
`createTocMappingV2` runs `pendingIds.filter(id => id !== initSubmitter.initiative_id)`
(results-toc-results.service.ts:1596) and `resultRequest` refuses to share a result with its
own owner (share-result-request.service.ts:183).

Reproduced on result 9409 (owner SP01): the PATCH carried pending=[50, 51] and only 51 came
back. Verified on screen after the fix β€” searching "Breeding" in the dropdown now returns
nothing, while SP02 and SP03 stay selectable.

Both sibling forms already exclude the owner and this one lost the filter in the P25
migration: rd-contributors-and-partners.component.ts:728 and ipsr-contributors-toc.component.ts:38.

The list is recomputed only when the catalog or the owner lands, never in a template getter:
handing app-pr-multi-select a new array on every change-detection pass is one of the two
NG0103 loop conditions for that control.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ugh the shared token enum

QA found the result-detail rail painting `Quality Assessed` with the APPROVED green while the
Results Center table one click away painted it cyan, and `Approved` falling through to the grey
"unknown status" fallback because the rail's private map had no entry for it (Cami, Slack video,
21-Sep). Five surfaces each carried their own copy of the status palette and they disagreed on
Editing, Pending review and Draft as well.

`result-status-tokens.ts` becomes the one place: it now carries the canonical LABEL per status id
alongside the fg/bg pair, and accepts the `string | number` the detail endpoint actually answers, so
no caller has a reason to coerce and re-map on its own. The result-detail rail, the bilateral page
header, the Review External Result drawer, the bilateral results list, the drafts list and the
Results Center table all resolve through it.

Pending review moves off grey onto the Submitted pair: grey reads as "inactive / disabled" on a
result that is mid-flight, and it sits on the same rung of the flow as Submitted does for W1/W2.
Editing's amber was ruled out because both statuses share a column in the Results Center, where two
identical chips would mean opposite things. No seventh colour was invented (UI-RULES rule 9).

Also removes a trailing SCSS override in the Results Center that repainted Approved and Rejected
with the legacy PRMS green and red while keeping the token background β€” half of one pair on half of
another, which is exactly what rule 9 forbids.

One existing test had the defect frozen into it (it demanded the approved green for Quality
Assessed) and was corrected; six lock tests were added so a future private map cannot pass by
looking neutral.

Gate: client `npm run build:dev` green, `ng lint` clean, 597/597 suites and 11110 tests passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sleeping 600ms

Jenkins build #2354 went red on this single assertion while the same test passed three times in
a row locally, and a red build blocks prtest for the whole team.

copyTable clears flatFormat through two NESTED 200ms setTimeouts, so the fixed wait(600) left
only 200ms of slack: enough on a laptop, not on a CI agent running 597 suites. The wait is now a
poll on the rendered class with a 5s deadline.

The guard keeps its teeth: with the repaint blocked the deadline runs out and the assertions
fail, verified before committing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ry and description in the setup drawer

[SPEC:quick/bilateral-drawer-project-details]

The creation flow moved into the Set up bilateral result drawer, and that drawer only
rendered an unlabelled snippet (summary || description) clamped to two lines, so AC2/AC3
could not be verified anywhere in the live flow.

The drawer now renders one labelled read-only block per field, and only for the fields
CLARISA actually filled: 2026 projects mostly arrive with summary null and the text in
description, and either field can come back as an empty string rather than null. When
neither exists an explicit empty state says so, which keeps no data distinguishable from
the field is missing. Long text keeps the two-line clamp behind a Read more toggle, since
the drawer is now the only place this text is readable.

Normalisation and de-duplication live in BilateralManualCreateFlowService, so the drawer
receives text that is already trimmed and free of values that merely repeat the project
title or the other field.

Verified: jest --testPathPattern=pages/bilateral 70 suites / 2344 tests pass, ng lint clean.

Triviality gate for akili-quick did not pass (conditional render, toggle state and data
normalisation across three components); fast-tracked on explicit instruction and recorded
as such in the quick log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…percentage to the bilateral Project Information

P2-3352 section 6 specifies a Contribution field (numeric + %, default 100, editable only while
the result is in Editing) in Section 1 of the W3/Bilateral form. It rendered nowhere, and there
was nowhere to store it either: results_by_projects carried only is_lead.

Server: additive nullable decimal(5,2) column, entity, an optional 0-100 DTO field, and the four
write branches of PATCH primary-assignment. Omitting the key leaves the stored value untouched,
so an older client cannot blank it; NULL means never answered and reads back as the 100 default.

Client: the field renders through app-pr-input inside the existing project block, read-only once
the result leaves Editing, and rides the section save. The save button now names what it saves
instead of always promising a project and program change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s the primary reference

[SPEC:quick/developers-official-docs]

The right-column card promised the full field reference and sent the reader to the
Swagger UI of the ingest API. The authoritative reference is the public Notion page
PRMS Normalizer - Technical Field Documentation, which is where the field rules, the
minimum data standards, the CLARISA catalogues and the breaking changes log actually
live; Swagger only lists request and response shapes and lets you fire a test call.

The card is now Official documentation: Notion is the primary CTA, Swagger stays as a
secondary link under a divider, and the copy states which one wins when the two
disagree. Step 2 of How to integrate was renamed to match. The page also notes that
Notion needs no account, since the route itself is behind CheckLoginGuard and the
integrator reading this may have no PRMS session.

Verified public without login before linking it: publicAccessRole reader,
requireLogin false.

officialDocsUrl is hardcoded next to the six API Gateway URLs already in the component
rather than read from environment.ts, because this page lists test and production side
by side and is a reference table, not the current environment.

Verified: scoped ng lint on the pages ts and html clean, jest developers 10/10 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lves

[SPEC:quick/developers-prod-bulk-host]

The production bulk ingest row carried b1a4fsvgni with the digit one. That host has no
DNS record: curl answers Could not resolve host. The live gateway is bla4fsvgni with the
letter l, which answers 401 Missing x-api-key. The two are indistinguishable by eye in a
proportional font, so a spec assertion now locks the working one in and rejects the other.

Two smaller corrections found while verifying the endpoints against the published
OpenAPI document, v1.2.0 and identical on both gateways:

The Set up webhooks button carried a #/Webhooks fragment. The document declares no tags,
so swagger-ui groups every operation as default and renders no such section: the anchor
resolved to nothing. Dropped it, and named the two operations in the copy instead, since
POST /webhook registers the destination and GET /webhook reads it back.

Both doc buttons open the test gateway and did not say so. The page stays deliberately
NOT environment-aware, per explicit decision: it is a reference that lists every
environment at once rather than a view of the one serving it, so somebody on test may
well want to follow the production link. That only works if each link states which
environment it opens, so the copy now does, and the production Swagger stays one card
below in Environments.

Endpoints were probed read-only, GET only, never POST.

Verified: scoped ng lint on the pages ts and html clean, jest developers 12/12 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… form, not only an admin

A Center User of the result's lead centre could open a W3/Bilateral result in Editing and not
find a single control: every custom-field renders its value as plain text while the GLOBAL
RolesService.readOnly flag is up (pr-multi-select.component.html:16, and the same line in
pr-input, pr-select, pr-textarea). That flag is a W1/W2 mechanism β€” it starts TRUE for anyone
who is not an application admin (roles.service.ts:75) and is only lowered when a W1/W2 result
is loaded for a member of its INITIATIVE (current-result.service.ts:52), a path bilateral never
walks. In practice only admins could edit a bilateral result, which no requirement asks for.
Measured on prtest result 9553 with the reporter's real roles: 6 app-pr-input hosts, 0 <input>.

The editor now answers that flag with the question the server already asks before accepting a
write β€” Center User (role 9) on the lead centre's CLARISA code, as validationCenterPermissions
does β€” combined with the existing status gate, and restores the application default on destroy
so the permission never leaks into a W1/W2 screen.

RolesService gains a rolesVersion counter: `roles` is a plain property, so a computed derived
from getMyCenters() cached the empty list it saw before the roles GET resolved and would have
left a legitimate Center User locked out for the whole visit on a cold start.

Verified in a real browser against prtest with that user's roles injected β€” locked before, 7
editable fields after β€” with the negative control of a Center User of a different centre, which
stays read-only exactly as the server's 403 would. Client suite 11143 tests and build:dev pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ry and description in the setup drawer

[SPEC:quick/bilateral-drawer-project-details]

The creation flow moved into the Set up bilateral result drawer, and that drawer only
rendered an unlabelled snippet (summary || description) clamped to two lines, so AC2/AC3
could not be verified anywhere in the live flow.

The drawer now renders one labelled read-only block per field, and only for the fields
CLARISA actually filled: 2026 projects mostly arrive with summary null and the text in
description, and either field can come back as an empty string rather than null. When
neither exists an explicit empty state says so, which keeps no data distinguishable from
the field is missing. Long text keeps the two-line clamp behind a Read more toggle, since
the drawer is now the only place this text is readable.

Normalisation and de-duplication live in BilateralManualCreateFlowService, so the drawer
receives text that is already trimmed and free of values that merely repeat the project
title or the other field.

Verified: jest --testPathPattern=pages/bilateral 70 suites / 2344 tests pass, ng lint clean.

Triviality gate for akili-quick did not pass (conditional render, toggle state and data
normalisation across three components); fast-tracked on explicit instruction and recorded
as such in the quick log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uzman-io/analyze-bilateral-api-docs

# Conflicts:
#	docs/specs/quick/quick-log.md
…General Information

UAT return from Cristian (bilateral result 9180): Contributors & Partners,
Geographic Location and Evidence show the standard MDS note at the top, but
General Information still did not.

The 2026-08-18 fix had moved it up. The 2026-09-04 style change (9f4cc79)
moved the banner + Full metadata toggle below the MDS fields, and the note
travelled down with it because here β€” unlike Contributors β€” the note and the
button shared a single container.

Split into two blocks, as Contributors already had them: the standard MDS
note at the top of the section, and the "fields above" banner with the
Complete full metadata button left untouched below the fields.

[SPEC:quick/bilateral-mds-note-general-info]

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rchive spec and record kaizen entry

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-only once a result leaves Editing

Both app-sub-geoscope instances (main scope and extra scope) were wired to a hard
[readOnly]="false" since a3a7156, so a result in Pending Review, Approved or Rejected still
offered the country delete icon, the sub-national picker and the per-entry remove icon. The
autosave refuses every write in that state (P2-3520), so the screen was offering edits that could
never persist: remove an entry, reload, and it is still there. The component already honours
readOnly - the binding was the defect, not the component.

The new spec asserts on the markup rather than on a rendered fixture, because the sibling spec
stubs the template with overrideTemplate and is blind to this class of defect - the same hole that
let it through in section-contributors. Negative control run: reintroducing the literal turns 3 of
its 4 cases red.

Scope note for the ticket: the two controls named in the report - the region chips and the
countries Yes/No - do NOT reproduce on prtest today. Measured on result 9464 (AfricaRice, Pending
Review, status 5): 0 clickable multi-select triggers, the Yes choice ignores the click, and no
PATCH or POST leaves the page. What was found instead is this sibling hole in the same section,
fixed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…k like the API one

The Official documentation card ends in an inline violet text link to the test Swagger,
while the Result decision webhooks card used an outlined button for the same kind of
action. Both open the same Swagger, so they now share the link styling.

The surrounding copy said the button opens the test Swagger and no longer would, so it
now reads the same way as the other card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ot the text link

Corrects the direction of 2780a3d: the webhooks action was the reference design, so
the Swagger action in the Official documentation card now matches it instead of the
other way round. Both open a Swagger and both are outlined buttons.

Copy follows: the documentation card says the button below opens the test environment,
and the webhooks card goes back to saying the button opens the test Swagger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dragging a column resize handle and releasing the mouse over the header
cell (not back over the thin resizer strip) made the browser synthesize
a native click on the <th>, which PrSortableColumnDirective and
validateOrder() both read as a sort request. onResizeStart now registers
a capture-phase, one-shot click guard on document for the duration of a
resize drag so that phantom click is swallowed before it reaches either
listener, with cleanup on mouseup/ngOnDestroy so a later, unrelated click
is never affected.

Verified: new regression test is red on the unmodified component and
green with the fix; confirmed against the real running app (not just
jsdom) by driving the exact mousedown/mousemove/mouseup/click sequence
on the live <th> and observing the guard prevent it.

Spec: docs/specs/bugfix/results-center-title-resize-triggers-sort/

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ment-2026-mc

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cted tables

Extends bugfix/results-center-title-resize-triggers-sort beyond the
Results Center table it originally covered. The same phantom-click
mechanism (dragging a column resizer and releasing over the header
synthesizes a native click that the header's sort listener reads as a
sort request) is independently duplicated β€” copy-pasted, not shared β€”
in three other tables, confirmed by reading each one's resize block in
full:

- programme-results.component.ts (Programme Results)
- bilateral-results-list.component.ts (Bilateral centre results)
- portfolio-overview.component.ts (Portfolio Overview matrix β€” adapted
  to its inline-closure onResizeStart and its sortBy(column) click
  binding instead of PrSortableColumnDirective)

Each gets the same capture-phase, one-shot document click guard,
matching that file's existing style. Also confirmed, and recorded as
explicitly out of scope, that links-to-results-global.component.ts and
both mapped-results-modal.component.ts files have no resize handle at
all (grep for mousedown/resize: zero hits) β€” they only share the
unrelated validateOrder sort helper, so no fix is needed there.

Verified: one new regression test per fixed file, each red on the
unmodified component (confirmed via git stash) and green with the fix.
Full targeted suite: 220/220 passing. ng lint: clean.

Spec updated: docs/specs/bugfix/results-center-title-resize-triggers-sort/
(Premise Ledger P-4..P-8, RCR-R-4/AC-3, RCR-DD-2, RCR-T-2..T-4)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…es from qa-development-2026-mc

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@JuanGuzman-io JuanGuzman-io self-assigned this Sep 23, 2026
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required β‰₯ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

* assertions below forbid, and a comment is not markup. Without this the spec fails on its own
* explanation β€” which is a false alarm, and a spec that cries wolf gets deleted.
*/
const template = rawTemplate.replace(/<!--[\s\S]*?-->/g, '');
@Cristian45
Cristian45 merged commit 0266741 into master Sep 23, 2026
7 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants