Skip to content

feat(#10240): hide target counts past goal#10772

Open
cliftonmcintosh wants to merge 15 commits intomedic:masterfrom
cliftonmcintosh:feat/10240-hide-target-counts-past-goal
Open

feat(#10240): hide target counts past goal#10772
cliftonmcintosh wants to merge 15 commits intomedic:masterfrom
cliftonmcintosh:feat/10240-hide-target-counts-past-goal

Conversation

@cliftonmcintosh
Copy link
Copy Markdown
Contributor

@cliftonmcintosh cliftonmcintosh commented Mar 27, 2026

Description

Outdated goals

This original approach has been superseded. See the Updated goals immediately below. For more context, please read the discussion.

  1. Layout overlap: When a count number is large, it overlapped with the "Goal X" label. Fixed by converting the .count block to a flex column layout so the goal label and count number stack vertically instead of colliding.

  2. Hide count past goal: Adds a new permission can_hide_target_count_past_goal. When a user has this permission, the count number is hidden on any target where pass >= goal. The goal label remains visible. This behavior is off by default (empty roles array in the default config).

Updated goals

  1. Fix the layout overlap by centering the goal.
  2. Add a field to the target object that allows people to specify that when the target is reached or exceeded, the UI is limited to showing the goal as the big number. For example if the target goal is 5000 and a user has reached a target count 10000, the UI for that target would show "Target 5000" in the small text at the top and "5000" in the larger text in the tile.
  3. Any target data structure that does not have this field or does not have it set to true will default to the existing behavior -- the target count will be displayed even when it reaches or exceeds the goal.
  4. No feature flag.

Closes #10240

Companion PRs

CHT-CONF: medic/cht-conf#800
CHT-DOCS: medic/cht-docs#2176

Questions

Layout overlap fix — approach feedback

I fixed the overlap by replacing the absolute-positioned goal label with a stacked flex layout (.count becomes display: flex; flex-direction: column; align-items: center, with the goal label above the count number, both centered). An alternative I considered was keeping the goal label in the top-right corner and adding right padding to .number when .has-goal is present (e.g., .has-goal .count .number { padding-right: 80px; }), which would be a smaller change but would offset the count number from true center. The padding approach would keep the count number visually off-center — it would be centered over the remaining space after reserving room for the goal label, not centered over the full card width. So on a wide card with a goal label, the big number would appear shifted left rather than truly centered. This approach is also fragile — it's a magic number that works for typical cases but can't guarantee no overlap for arbitrarily large numbers. Does the centered stacked layout work for you, or would you prefer the top-right approach?

Here is an example of the centering approach centering-approach
Here is an example of the padding approach padding-approach

What to hide

First off, if we center the goal, then do we need hiding? I've included it, but I want to raise the possibility that it might not be needed if we center the goal. I'm not sure what the use cases are, so I defer to you.

In one of @dianabarsan's comments, she said we should be

adding a setting to not display the goal OR target count past the goal

I chose to not display the target count because that was what I understood the suggestion to be. The example feature flag (can_hide_target_count_past_goal) mentioned in the issue points to that as the approach that was wanted, but it feels to me like having an empty target makes it look like the data is missing or the person has not met the goal.

What is preferred? Hiding the target? Hiding the goal? Something else? Here are some screenshots with different options. I think Option 2 works best.

I also wonder if maybe this isn't needed if we center both the goal and the target.

Option 1: Currently implemented -- hide only the target when goal is met or exceeded Image
Option 2: Hide only the goal when goal is met or exceeded Image
Option 3: Hide both the target and the goal when goal is met or exceeded Image
Option 4: Include a visual element showing the goal is met Image

AI Disclosure

I used Claude Code to help navigate the codebase, plan the approach, and write tests. I made all design decisions, reviewed all code, and verified the solution manually in the browser. I attributed Claude as a co-author on all commits that included the use of Claude Code.

If you would like to see them, I can provide a copy of the directions I gave Claude Code when starting the planning.

Code review checklist

  • UI/UX backwards compatible: Test it works for the new design (enabled by default). And test it works in the old design, enable can_view_old_navigation permission to see the old design. Test it has appropriate design for RTL languages.
  • Readable: Concise, well named, follows the style guide
  • Documented: Configuration and user documentation on cht-docs
  • Tested: Unit and/or e2e where appropriate
  • Internationalised: All user facing text
  • Backwards compatible: Works with existing data and configuration or includes a migration. Any breaking changes documented in the release notes.
  • AI disclosure: Please disclose use of AI per the guidelines.

Visual alignment fix for count widgets

I adjusted the alignment of the target tiles to aligns the count number display across count widgets that have a goal and those that do not.
Previously, the number position differed between the two variants, causing visual inconsistency
when both types appeared on the same Targets tab. Fixed by adding a minimum height to the number
container and using content justification to keep the number vertically centered regardless of
whether a goal is present.

Testing done

Unit tests

14 unit tests cover the AnalyticsTargetsComponent in
webapp/tests/karma/ts/modules/analytics/analytics-targets.component.spec.ts.
The following cases are specifically tested for the limit_count_to_goal behavior:

  • limit_count_to_goal: true, pass > goal → displays goal as the count number
  • limit_count_to_goal: true, pass === goal (boundary case) → displays goal
  • limit_count_to_goal: true, pass < goal → displays actual pass value
  • limit_count_to_goal absent → displays actual pass value even when goal is exceeded
  • limit_count_to_goal: true, goal: -1 (no goal) → displays actual pass value

All 14 tests pass.

Browser verification with dummy data

A dummy target (Earned Points, goal: 20000) was temporarily added to the component
to visually verify behavior in the browser under the following conditions:

Condition limit_count_to_goal pass goal Displayed
Count exceeds goal, field set true 50,000 20,000 20,000 ✓
Count exceeds goal, field not set absent 50,000 20,000 50,000 ✓
Count below goal, field set true 19,999 20,000 19,999 ✓
Count equals goal (boundary), field set true 20,000 20,000 20,000 ✓

Screenshots from testing

Screenshot: limit when target exceeds goal limit-when-target-exceeds-goal
Screenshot: no limit enforced when target exceeds goal no-limit-target-exceeds-goal
Screenshot: limit when target less than goal limit-when-target-less-than-goal
Screenshot: limit when target equals goal limit-when-target-equals-goal
Screen recording: mobile-device-sized view
mobile-device-view-when-target-exceeds-goal.mov

End-to-end verification with real config data

The active-pregnancies target in config/default/targets.js was temporarily modified
to goal: 20, limit_count_to_goal: true and uploaded to the running local instance using
the local cht-conf build (which includes the companion schema and parse-targets changes).

After logging in as a CHW user with 33 active pregnancies, the target card correctly
displayed 20 (the goal) instead of 33 (the actual count), confirming the full data flow:

targets.jscht-conf compile → CouchDB → rules-enginetarget-state.js → component → UI

Screenshot: testing limit in view with active pregnancies testing-with-active-pregnancies

License

The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

@dianabarsan or @mrjones-plip

Who should I ask to review this?

cliftonmcintosh and others added 5 commits March 27, 2026 16:42
Replace absolute positioning of goal label with flex column layout
to prevent overlap with large count numbers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssion

Add can_hide_target_count_past_goal permission to control whether
the count number is hidden for targets where pass >= goal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion and goal combinations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…default config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cliftonmcintosh cliftonmcintosh force-pushed the feat/10240-hide-target-counts-past-goal branch from bb658e7 to 95e1d0f Compare March 27, 2026 21:42
Comment on lines +72 to +74
display: flex;
flex-direction: column;
align-items: center;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centering the goal seems cleaner than trying to pad it in an absolute position in the top right corner. See my question about this in the PR description.

@mrjones-plip
Copy link
Copy Markdown
Contributor

She's offline already, but @dianabarsan should be able to pick this up next week - thanks for reaching out!

FYI we're working with a bunch of new community members who are submitting a big collection of PRs - might be a hot minute but we'll get to this!

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cliftonmcintosh cliftonmcintosh changed the title Feat/10240 hide target counts past goal feat(#10240) hide target counts past goal Mar 28, 2026
@cliftonmcintosh cliftonmcintosh changed the title feat(#10240) hide target counts past goal feat(#10240): hide target counts past goal Mar 28, 2026
Copy link
Copy Markdown
Member

@dianabarsan dianabarsan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick work. I think the central alignment is the best (but I'm not a UX expert).

As for hiding the target count when it exceeds the goal, I believe we weren't trying to hide the actual number (like in your screenshots), rather just show the goal number instead of the actual target number. It's possible using a permission is not the best approach, maybe only some targets need their goals hidden?

I believe the context for hiding the exceeding number past the goal was in the cases where the target counts provided remuneration, in some cases once the "ceiling" (goal) was reached, not additional remuneration was offered.
for example, the chw would get paid:

  • $50 for a score of 500,
  • $100 for a target score of 1000 (the goal / ceiling)
  • $100 for a target score of 1500

Users would sometimes expect to be paid $200 if their target showed 2000. So then, these targets specifically would not show the actual number if it exceeded the goal.

For other targets, for example for pregnancy or delivery targets, i see absolutely no reason to hide the actual target number.

Comment on lines +71 to +73
this.authService.has(HIDE_COUNT_PAST_GOAL_PERMISSION).then(has => {
this.hideCountWhenGoalMet = has;
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be simplified as ?

Suggested change
this.authService.has(HIDE_COUNT_PAST_GOAL_PERMISSION).then(has => {
this.hideCountWhenGoalMet = has;
});
this.hideCountWhenGoalMet = await this.authService.has(HIDE_COUNT_PAST_GOAL_PERMISSION);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. I had to define ngOnInit as async, so I made a commit locally and pushed it

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

cliftonmcintosh commented Mar 30, 2026

As for hiding the target count when it exceeds the goal, I believe we weren't trying to hide the actual number (like in your screenshots), rather just show the goal number instead of the actual target number.

Ahhh. That makes sense.

I should be able to adjust it so that when the flag is set and the goal is exceeded, we show the goal instead of the target count.

For other targets, for example for pregnancy or delivery targets, i see absolutely no reason to hide the actual target number.

Is there a way to distinguish which targets should not be hidden?

It's possible using a permission is not the best approach, maybe only some targets need their goals hidden?

Given the possiblity that some targets might not be hidden (like pregnancy or delivery targets) while some might be, what about an alternative approach like this:

Add a new field to the target data structure that allows people to say the target can't exceed the goal. Once the goal is reached, either the app stops incrementing the data or, if it does increment, we use that new field to decide to limit the target count in the UI to the goal amount.

Co-Authored-By: Diana Barsan <35681649+dianabarsan@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dianabarsan
Copy link
Copy Markdown
Member

I believe adding a field to the target configuration is the way to go. There is no such field at the moment (hence the feature request :D :D )

either the app stops incrementing the data

I think this will have more implications. I think we should just manifest this setting in the UI and have the target document still register the actual number.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

I believe adding a field to the target configuration is the way to go.

@dianabarsan Thanks. Based on the feedback so far, can I state what I think the goals are?

  1. Fix the overlap by centering the goal (like in the screenshots I've provided)
  2. Add a field to the target object that allows people to specify that when the target is reached or exceeded, the UI is limited to showing the goal as the big number. For example if the target goal is 5000 and a user has reached a target count 10000, the UI for that target would show "Target 5000" in the small text at the top and "5000" in the larger text in the tile.
  3. Any target data structure that does not have this field or does not have it set to true will default to the existing behavior -- the target count will be displayed even when it reaches or exceeds the goal.
  4. No feature flag

Does that capture what we're thinking?

@dianabarsan
Copy link
Copy Markdown
Member

@cliftonmcintosh nailed it!!

cliftonmcintosh and others added 4 commits March 31, 2026 12:55
This reverts the changes made in the following commits:
- da8c6a3
- 13d2919
- 5b305d3
- 95e1d0f
- e2ef816
- 8247ce0
…al is set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… goals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…et aggregation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines +75 to +77
justify-content: flex-end;
text-align: center;
min-height: 74px;
Copy link
Copy Markdown
Contributor Author

@cliftonmcintosh cliftonmcintosh Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change for justify-content and min-height aligns the count number display across count widgets that have a goal and those that do not.
Previously, the number position differed between the two variants, causing visual inconsistency
when both types appeared on the same Targets tab. Fixed by adding a minimum height to the number container and using content justification to keep the number vertically centered regardless of whether a goal is present.

Before

See how the big number sits down lower whenever there is a goal?

Image
After

Now the big numbers stay at the same height in targets that have goals and targets that don't have goals.

Image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I don't like the big number offset either. I'm wondering whether the best visual solution is to move the goal underneath the number and conditionally display it. Then we won't have all this extra padding.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether the best visual solution is to move the goal underneath the number and conditionally display it. Then we won't have all this extra padding.

I can work up that as an option and provide a screenshot. Or if you are confident that is a change to make, I can go ahead and do it. Let me know, please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the goal so it is below. Here is a screenshot. Tell me what you think, please.

Screenshot 2026-04-03 at 13 50 59

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

Updated based on our revised approach.

I've added a "Testing done" section to the description. It has screenshots of various scenarios.

I've also opened companion PRs for cht-conf and cht-docs.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

Will the test at tests/e2e/visual/targets/targets-overview.wdio-spec.js need any adjustment? It ends with a call to generateScreenshot('targets', 'overview'). Is there some sort of reference screenshot that we will need to update to go with the visual changes?

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

cliftonmcintosh commented Apr 1, 2026

As I understand it, if we want an e2e test for the new field, we will depend on the cht-conf PR being
merged and the cht-conf package version bumped in cht-core before we can write a successful e2e test for a target that has limit_count_to_goal: true and a goal lower than the count. Is that accurate?

If so, I think that means we would pause this until a new version of cht-conf is published and can be brought in as a dependency in cht-core.

@dianabarsan
Copy link
Copy Markdown
Member

I didn't end up having space for this today. I'll check on it tomorrow.

@dianabarsan
Copy link
Copy Markdown
Member

dianabarsan commented Apr 3, 2026

Will the test at tests/e2e/visual/targets/targets-overview.wdio-spec.js need any adjustment? It ends with a call to generateScreenshot('targets', 'overview'). Is there some sort of reference screenshot that we will need to update to go with the visual changes?

No, you can safely ignore those. They haven't been introduced in our CI suite and not run regularly.
As for reference screenshot! Yes, we will need to update our docs site, but that can come before releasing.

@dianabarsan
Copy link
Copy Markdown
Member

dianabarsan commented Apr 3, 2026

As I understand it, if we want an e2e test for the new field, we will depend on the cht-conf PR being merged and the cht-conf package version bumped in cht-core before we can write a successful e2e test for a target that has limit_count_to_goal: true and a goal lower than the count. Is that accurate?

If so, I think that means we would pause this until a new version of cht-conf is published and can be brought in as a dependency in cht-core.

Not necessarily. You can install a branch cht-conf version instead of the latest release in this branch, and then the property will be available to use.

npm install github:cliftonmcintosh:feat/10240-hide-target-counts-past-goal

We would, indeed, need to publish the cht-conf package before merging this PR, as reverting to the latest released cht-conf will be required.

Copy link
Copy Markdown
Member

@dianabarsan dianabarsan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far excellent work, as usual!
I added one question about alternative styling and replied to the e2e questions.

Comment on lines +75 to +77
justify-content: flex-end;
text-align: center;
min-height: 74px;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I don't like the big number offset either. I'm wondering whether the best visual solution is to move the goal underneath the number and conditionally display it. Then we won't have all this extra padding.

}

getDisplayCount(target: any): number {
if (target.limit_count_to_goal && target.goal >= 0 && target.value?.pass >= target.goal) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awww, negative goals are out? is the goal positive check necessary?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see, it's in case someone creates a target without a goal and then sets limit_count_to_goal to true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's in case someone creates a target without a goal and then sets limit_count_to_goal to true

Yes!

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

You can install a branch cht-conf version instead of the latest release in this branch, and then the property will be available to use.

If it makes it easier to review, I can go ahead and do this and then we can revert to the latest after the cht-conf change is merged. Please let me know if you prefer that or prefer to wait until the cht-conf changes are merged.

@dianabarsan
Copy link
Copy Markdown
Member

If it makes it easier to review, I can go ahead and do this and then we can revert to the latest after the cht-conf change is merged. Please let me know if you prefer that or prefer to wait until the cht-conf changes are merged.

We have done this numerous times, I prefer it and suggest it. No reason to block on the other work.

cliftonmcintosh and others added 3 commits April 3, 2026 13:38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…across target cards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

If it makes it easier to review, I can go ahead and do this and then we can revert to the latest after the cht-conf change is merged. Please let me know if you prefer that or prefer to wait until the cht-conf changes are merged.

We have done this numerous times, I prefer it and suggest it. No reason to block on the other work.

I've added e2e tests with the cht-conf dependency set to my branch.

"chai-shallow-deep-equal": "^1.4.6",
"chai-things": "^0.2.0",
"chokidar": "^4.0.3",
"cht-conf": "^6.0.2",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder that this needs to be changed before merging.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this file are because of the temporary change to the cht-conf dependency. They should be reverted when we update that dependency again.

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.

Add the option to hide target counts past the goal

3 participants